git: 8bba2c0f8958 - main - nvmf: Refactor reconnection support
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Fri, 24 Jan 2025 14:51:19 UTC
The branch main has been updated by jhb: URL: https://cgit.FreeBSD.org/src/commit/?id=8bba2c0f8958443790b1f3abc0675719da987e87 commit 8bba2c0f8958443790b1f3abc0675719da987e87 Author: John Baldwin <jhb@FreeBSD.org> AuthorDate: 2025-01-24 14:43:19 +0000 Commit: John Baldwin <jhb@FreeBSD.org> CommitDate: 2025-01-24 14:43:19 +0000 nvmf: Refactor reconnection support Save more data associated with a new association including the network address of the remote controller. This permits reconnecting an association without providing the address or other details. To use this new mode, provide only an existing device ID to nvmecontrol's reconnect command. An address can still be provided to request a different address or other different settings for the new association. The saved data includes an entire Discovery Log page entry to aim to be compatible with other transports in the future. When a remote controller is connected to via a Discovery Log page entry (nvmecontrol connect-all), the raw entry is used. When a remote controller is connected to via an explicit address, an entry is synthesized from the parameters. Note that this is a pseudo-ABI break for the ioctls used by nvmf(4) in that the nvlists for handoff and reconnect now use a slightly different set of elements. Since this is only present in main I did not bother implementing compatability shims. Sponsored by: Chelsio Communications Differential Revision: https://reviews.freebsd.org/D48214 --- lib/libnvmf/internal.h | 4 + lib/libnvmf/libnvmf.h | 17 ++- lib/libnvmf/nvmf_host.c | 77 ++++++++++++-- lib/libnvmf/nvmf_tcp.c | 22 ++++ lib/libnvmf/nvmf_transport.c | 9 ++ sbin/nvmecontrol/connect.c | 28 ++++- sbin/nvmecontrol/fabrics.c | 35 ++++-- sbin/nvmecontrol/fabrics.h | 15 ++- sbin/nvmecontrol/nvmecontrol.8 | 22 +++- sbin/nvmecontrol/reconnect.c | 234 ++++++++++++++++++++++++++++++++--------- sys/dev/nvmf/host/nvmf.c | 54 ++++++---- sys/dev/nvmf/host/nvmf_var.h | 2 + sys/dev/nvmf/nvmf.h | 14 ++- 13 files changed, 424 insertions(+), 109 deletions(-) diff --git a/lib/libnvmf/internal.h b/lib/libnvmf/internal.h index 7b3d4fbb03ef..300776c1213b 100644 --- a/lib/libnvmf/internal.h +++ b/lib/libnvmf/internal.h @@ -26,6 +26,8 @@ struct nvmf_transport_ops { /* Add params for kernel handoff. */ void (*kernel_handoff_params)(struct nvmf_qpair *qp, nvlist_t *nvl); + int (*populate_dle)(struct nvmf_qpair *qp, + struct nvme_discovery_log_entry *dle); /* Capsule operations. */ struct nvmf_capsule *(*allocate_capsule)(struct nvmf_qpair *qp); @@ -111,6 +113,8 @@ void na_clear_error(struct nvmf_association *na); void na_error(struct nvmf_association *na, const char *fmt, ...); int nvmf_kernel_handoff_params(struct nvmf_qpair *qp, nvlist_t **nvlp); +int nvmf_populate_dle(struct nvmf_qpair *qp, + struct nvme_discovery_log_entry *dle); int nvmf_pack_ioc_nvlist(struct nvmf_ioc_nv *nv, nvlist_t *nvl); #endif /* !__LIBNVMF_INTERNAL_H__ */ diff --git a/lib/libnvmf/libnvmf.h b/lib/libnvmf/libnvmf.h index 44f13fda5ddd..f34ccdb177e7 100644 --- a/lib/libnvmf/libnvmf.h +++ b/lib/libnvmf/libnvmf.h @@ -320,6 +320,14 @@ int nvmf_host_identify_namespace(struct nvmf_qpair *qp, uint32_t nsid, int nvmf_host_fetch_discovery_log_page(struct nvmf_qpair *qp, struct nvme_discovery_log **logp); +/* + * Construct a discovery log page entry that describes the connection + * used by a host association's admin queue pair. + */ +int nvmf_init_dle_from_admin_qp(struct nvmf_qpair *qp, + const struct nvme_controller_data *cdata, + struct nvme_discovery_log_entry *dle); + /* * Request a desired number of I/O queues via SET_FEATURES. The * number of actual I/O queues available is returned in *actual on @@ -332,7 +340,8 @@ int nvmf_host_request_queues(struct nvmf_qpair *qp, u_int requested, * Handoff active host association to the kernel. This frees the * qpairs (even on error). */ -int nvmf_handoff_host(struct nvmf_qpair *admin_qp, u_int num_queues, +int nvmf_handoff_host(const struct nvme_discovery_log_entry *dle, + const char *hostnqn, struct nvmf_qpair *admin_qp, u_int num_queues, struct nvmf_qpair **io_queues, const struct nvme_controller_data *cdata); /* @@ -359,8 +368,8 @@ int nvmf_reconnect_params(int fd, nvlist_t **nvlp); * Handoff active host association to an existing host in the kernel. * This frees the qpairs (even on error). */ -int nvmf_reconnect_host(int fd, struct nvmf_qpair *admin_qp, - u_int num_queues, struct nvmf_qpair **io_queues, - const struct nvme_controller_data *cdata); +int nvmf_reconnect_host(int fd, const struct nvme_discovery_log_entry *dle, + const char *hostnqn, struct nvmf_qpair *admin_qp, u_int num_queues, + struct nvmf_qpair **io_queues, const struct nvme_controller_data *cdata); #endif /* !__LIBNVMF_H__ */ diff --git a/lib/libnvmf/nvmf_host.c b/lib/libnvmf/nvmf_host.c index c3668600c463..e194522870d1 100644 --- a/lib/libnvmf/nvmf_host.c +++ b/lib/libnvmf/nvmf_host.c @@ -709,6 +709,27 @@ nvmf_host_fetch_discovery_log_page(struct nvmf_qpair *qp, return (0); } +int +nvmf_init_dle_from_admin_qp(struct nvmf_qpair *qp, + const struct nvme_controller_data *cdata, + struct nvme_discovery_log_entry *dle) +{ + int error; + uint16_t cntlid; + + memset(dle, 0, sizeof(*dle)); + error = nvmf_populate_dle(qp, dle); + if (error != 0) + return (error); + if ((cdata->fcatt & 1) == 0) + cntlid = NVMF_CNTLID_DYNAMIC; + else + cntlid = cdata->ctrlr_id; + dle->cntlid = htole16(cntlid); + memcpy(dle->subnqn, cdata->subnqn, sizeof(dle->subnqn)); + return (0); +} + int nvmf_host_request_queues(struct nvmf_qpair *qp, u_int requested, u_int *actual) { @@ -767,17 +788,23 @@ is_queue_pair_idle(struct nvmf_qpair *qp) } static int -prepare_queues_for_handoff(struct nvmf_ioc_nv *nv, struct nvmf_qpair *admin_qp, - u_int num_queues, struct nvmf_qpair **io_queues, - const struct nvme_controller_data *cdata) +prepare_queues_for_handoff(struct nvmf_ioc_nv *nv, + const struct nvme_discovery_log_entry *dle, const char *hostnqn, + struct nvmf_qpair *admin_qp, u_int num_queues, + struct nvmf_qpair **io_queues, const struct nvme_controller_data *cdata) { - nvlist_t *nvl, *nvl_qp; + const struct nvmf_association *na = admin_qp->nq_association; + nvlist_t *nvl, *nvl_qp, *nvl_rparams; u_int i; int error; if (num_queues == 0) return (EINVAL); + /* Ensure trtype matches. */ + if (dle->trtype != na->na_trtype) + return (EINVAL); + /* All queue pairs must be idle. */ if (!is_queue_pair_idle(admin_qp)) return (EBUSY); @@ -786,9 +813,35 @@ prepare_queues_for_handoff(struct nvmf_ioc_nv *nv, struct nvmf_qpair *admin_qp, return (EBUSY); } + /* Fill out reconnect parameters. */ + nvl_rparams = nvlist_create(0); + nvlist_add_binary(nvl_rparams, "dle", dle, sizeof(*dle)); + nvlist_add_string(nvl_rparams, "hostnqn", hostnqn); + nvlist_add_number(nvl_rparams, "num_io_queues", num_queues); + nvlist_add_number(nvl_rparams, "kato", admin_qp->nq_kato); + nvlist_add_number(nvl_rparams, "io_qsize", io_queues[0]->nq_qsize); + nvlist_add_bool(nvl_rparams, "sq_flow_control", + na->na_params.sq_flow_control); + switch (na->na_trtype) { + case NVMF_TRTYPE_TCP: + nvlist_add_bool(nvl_rparams, "header_digests", + na->na_params.tcp.header_digests); + nvlist_add_bool(nvl_rparams, "data_digests", + na->na_params.tcp.data_digests); + break; + default: + __unreachable(); + } + error = nvlist_error(nvl_rparams); + if (error != 0) { + nvlist_destroy(nvl_rparams); + return (error); + } + nvl = nvlist_create(0); - nvlist_add_number(nvl, "trtype", admin_qp->nq_association->na_trtype); + nvlist_add_number(nvl, "trtype", na->na_trtype); nvlist_add_number(nvl, "kato", admin_qp->nq_kato); + nvlist_move_nvlist(nvl, "rparams", nvl_rparams); /* First, the admin queue. */ error = nvmf_kernel_handoff_params(admin_qp, &nvl_qp); @@ -816,7 +869,8 @@ prepare_queues_for_handoff(struct nvmf_ioc_nv *nv, struct nvmf_qpair *admin_qp, } int -nvmf_handoff_host(struct nvmf_qpair *admin_qp, u_int num_queues, +nvmf_handoff_host(const struct nvme_discovery_log_entry *dle, + const char *hostnqn, struct nvmf_qpair *admin_qp, u_int num_queues, struct nvmf_qpair **io_queues, const struct nvme_controller_data *cdata) { struct nvmf_ioc_nv nv; @@ -829,8 +883,8 @@ nvmf_handoff_host(struct nvmf_qpair *admin_qp, u_int num_queues, goto out; } - error = prepare_queues_for_handoff(&nv, admin_qp, num_queues, io_queues, - cdata); + error = prepare_queues_for_handoff(&nv, dle, hostnqn, admin_qp, + num_queues, io_queues, cdata); if (error != 0) goto out; @@ -924,15 +978,16 @@ nvmf_reconnect_params(int fd, nvlist_t **nvlp) } int -nvmf_reconnect_host(int fd, struct nvmf_qpair *admin_qp, u_int num_queues, +nvmf_reconnect_host(int fd, const struct nvme_discovery_log_entry *dle, + const char *hostnqn, struct nvmf_qpair *admin_qp, u_int num_queues, struct nvmf_qpair **io_queues, const struct nvme_controller_data *cdata) { struct nvmf_ioc_nv nv; u_int i; int error; - error = prepare_queues_for_handoff(&nv, admin_qp, num_queues, io_queues, - cdata); + error = prepare_queues_for_handoff(&nv, dle, hostnqn, admin_qp, + num_queues, io_queues, cdata); if (error != 0) goto out; diff --git a/lib/libnvmf/nvmf_tcp.c b/lib/libnvmf/nvmf_tcp.c index 3f794b5d9750..6f41ca7ff502 100644 --- a/lib/libnvmf/nvmf_tcp.c +++ b/lib/libnvmf/nvmf_tcp.c @@ -8,9 +8,11 @@ #include <sys/endian.h> #include <sys/gsb_crc32.h> #include <sys/queue.h> +#include <sys/socket.h> #include <sys/uio.h> #include <assert.h> #include <errno.h> +#include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -1144,6 +1146,25 @@ tcp_kernel_handoff_params(struct nvmf_qpair *nq, nvlist_t *nvl) nvlist_add_number(nvl, "max_icd", qp->max_icd); } +static int +tcp_populate_dle(struct nvmf_qpair *nq, struct nvme_discovery_log_entry *dle) +{ + struct nvmf_tcp_qpair *qp = TQP(nq); + struct sockaddr_storage ss; + socklen_t ss_len; + + ss_len = sizeof(ss); + if (getpeername(qp->s, (struct sockaddr *)&ss, &ss_len) == -1) + return (errno); + + if (getnameinfo((struct sockaddr *)&ss, ss_len, dle->traddr, + sizeof(dle->traddr), dle->trsvcid, sizeof(dle->trsvcid), + NI_NUMERICHOST | NI_NUMERICSERV) != 0) + return (EINVAL); + + return (0); +} + static struct nvmf_capsule * tcp_allocate_capsule(struct nvmf_qpair *qp __unused) { @@ -1468,6 +1489,7 @@ struct nvmf_transport_ops tcp_ops = { .allocate_qpair = tcp_allocate_qpair, .free_qpair = tcp_free_qpair, .kernel_handoff_params = tcp_kernel_handoff_params, + .populate_dle = tcp_populate_dle, .allocate_capsule = tcp_allocate_capsule, .free_capsule = tcp_free_capsule, .transmit_capsule = tcp_transmit_capsule, diff --git a/lib/libnvmf/nvmf_transport.c b/lib/libnvmf/nvmf_transport.c index fa3826b8c50d..b105c17d2efb 100644 --- a/lib/libnvmf/nvmf_transport.c +++ b/lib/libnvmf/nvmf_transport.c @@ -259,6 +259,15 @@ nvmf_kernel_handoff_params(struct nvmf_qpair *qp, nvlist_t **nvlp) return (0); } +int +nvmf_populate_dle(struct nvmf_qpair *qp, struct nvme_discovery_log_entry *dle) +{ + struct nvmf_association *na = qp->nq_association; + + dle->trtype = na->na_trtype; + return (na->na_ops->populate_dle(qp, dle)); +} + const char * nvmf_transport_type(uint8_t trtype) { diff --git a/sbin/nvmecontrol/connect.c b/sbin/nvmecontrol/connect.c index ef614eca2e2a..c1d5d2cbaf5a 100644 --- a/sbin/nvmecontrol/connect.c +++ b/sbin/nvmecontrol/connect.c @@ -62,11 +62,14 @@ tcp_association_params(struct nvmf_association_params *params) static int connect_nvm_controller(enum nvmf_trtype trtype, int adrfam, const char *address, - const char *port, uint16_t cntlid, const char *subnqn) + const char *port, uint16_t cntlid, const char *subnqn, + const struct nvme_discovery_log_entry *dle) { struct nvme_controller_data cdata; + struct nvme_discovery_log_entry dle_thunk; struct nvmf_association_params aparams; struct nvmf_qpair *admin, **io; + const char *hostnqn; int error; memset(&aparams, 0, sizeof(aparams)); @@ -80,16 +83,31 @@ connect_nvm_controller(enum nvmf_trtype trtype, int adrfam, const char *address, return (EX_UNAVAILABLE); } + hostnqn = opt.hostnqn; + if (hostnqn == NULL) + hostnqn = nvmf_default_hostnqn(); io = calloc(opt.num_io_queues, sizeof(*io)); error = connect_nvm_queues(&aparams, trtype, adrfam, address, port, - cntlid, subnqn, opt.hostnqn, opt.kato * 1000, &admin, io, + cntlid, subnqn, hostnqn, opt.kato * 1000, &admin, io, opt.num_io_queues, opt.queue_size, &cdata); if (error != 0) { free(io); return (error); } - error = nvmf_handoff_host(admin, opt.num_io_queues, io, &cdata); + if (dle == NULL) { + error = nvmf_init_dle_from_admin_qp(admin, &cdata, &dle_thunk); + if (error != 0) { + warnc(error, "Failed to generate handoff parameters"); + disconnect_nvm_queues(admin, io, opt.num_io_queues); + free(io); + return (EX_IOERR); + } + dle = &dle_thunk; + } + + error = nvmf_handoff_host(dle, hostnqn, admin, opt.num_io_queues, io, + &cdata); if (error != 0) { warnc(error, "Failed to handoff queues to kernel"); free(io); @@ -140,7 +158,7 @@ connect_discovery_entry(struct nvme_discovery_log_entry *entry) /* XXX: Should this make use of entry->aqsz in some way? */ connect_nvm_controller(entry->trtype, adrfam, entry->traddr, - entry->trsvcid, entry->cntlid, entry->subnqn); + entry->trsvcid, entry->cntlid, entry->subnqn, entry); } static void @@ -198,7 +216,7 @@ connect_fn(const struct cmd *f, int argc, char *argv[]) cntlid = nvmf_parse_cntlid(opt.cntlid); error = connect_nvm_controller(trtype, AF_UNSPEC, address, port, cntlid, - opt.subnqn); + opt.subnqn, NULL); if (error != 0) exit(error); diff --git a/sbin/nvmecontrol/fabrics.c b/sbin/nvmecontrol/fabrics.c index 27ecc8fb6cdc..1d90a62dd3e3 100644 --- a/sbin/nvmecontrol/fabrics.c +++ b/sbin/nvmecontrol/fabrics.c @@ -7,6 +7,7 @@ #include <sys/socket.h> #include <netinet/in.h> +#include <arpa/inet.h> #include <err.h> #include <libnvmf.h> #include <netdb.h> @@ -48,6 +49,14 @@ init_hostid(void) return (true); } +const char * +nvmf_default_hostnqn(void) +{ + if (!init_hostid()) + exit(EX_IOERR); + return (nqn); +} + void nvmf_parse_address(const char *in_address, const char **address, const char **port, char **tofree) @@ -137,7 +146,7 @@ nvmf_parse_cntlid(const char *cntlid) } } -bool +static bool tcp_qpair_params(struct nvmf_qpair_params *params, int adrfam, const char *address, const char *port) { @@ -422,13 +431,10 @@ connect_nvm_queues(const struct nvmf_association_params *aparams, if (!init_hostid()) return (EX_IOERR); - if (hostnqn != NULL) { - if (!nvmf_nqn_valid(hostnqn)) { - warnx("Invalid HostNQN %s", hostnqn); - return (EX_USAGE); - } - } else - hostnqn = nqn; + if (hostnqn == NULL || !nvmf_nqn_valid(hostnqn)) { + warnx("Invalid HostNQN %s", hostnqn); + return (EX_USAGE); + } /* Association. */ na = nvmf_allocate_association(trtype, false, aparams); @@ -510,12 +516,19 @@ connect_nvm_queues(const struct nvmf_association_params *aparams, return (0); out: + disconnect_nvm_queues(*admin, io, num_io_queues); + nvmf_free_association(na); + return (error); +} + +void +disconnect_nvm_queues(struct nvmf_qpair *admin, struct nvmf_qpair **io, + u_int num_io_queues) +{ for (u_int i = 0; i < num_io_queues; i++) { if (io[i] == NULL) break; nvmf_free_qpair(io[i]); } - shutdown_controller(*admin); - nvmf_free_association(na); - return (error); + shutdown_controller(admin); } diff --git a/sbin/nvmecontrol/fabrics.h b/sbin/nvmecontrol/fabrics.h index 9d6ee24b88fb..1f43fc53bb8f 100644 --- a/sbin/nvmecontrol/fabrics.h +++ b/sbin/nvmecontrol/fabrics.h @@ -18,9 +18,11 @@ void nvmf_parse_address(const char *in_address, const char **address, uint16_t nvmf_parse_cntlid(const char *cntlid); -/* Returns true if able to open a connection. */ -bool tcp_qpair_params(struct nvmf_qpair_params *params, int adrfam, - const char *address, const char *port); +const char *nvmf_default_hostnqn(void); + +int nvmf_init_dle_from_address(enum nvmf_trtype trtype, const char *address, + const char *port, uint16_t cntlid, const char *subnqn, + struct nvme_discovery_log_entry *dle); /* Connect to a discovery controller and return the Admin qpair. */ struct nvmf_qpair *connect_discovery_adminq(enum nvmf_trtype trtype, @@ -38,4 +40,11 @@ int connect_nvm_queues(const struct nvmf_association_params *aparams, uint32_t kato, struct nvmf_qpair **admin, struct nvmf_qpair **io, u_int num_io_queues, u_int queue_size, struct nvme_controller_data *cdata); +/* + * Disconnect from an NVM controller disconnecting all queues and + * shutting down the controller. + */ +void disconnect_nvm_queues(struct nvmf_qpair *admin, struct nvmf_qpair **io, + u_int num_io_queues); + #endif /* !__FABRICS_H__ */ diff --git a/sbin/nvmecontrol/nvmecontrol.8 b/sbin/nvmecontrol/nvmecontrol.8 index e21079b17ed6..a9f4552fbdc6 100644 --- a/sbin/nvmecontrol/nvmecontrol.8 +++ b/sbin/nvmecontrol/nvmecontrol.8 @@ -235,6 +235,9 @@ .Aq Ar device-id | Ar namespace-id | Ar SubNQN .Nm .Ic reconnect +.Aq Ar device-id +.Nm +.Ic reconnect .Op Fl FGg .Op Fl i Ar queues .Op Fl k Ar seconds @@ -813,11 +816,20 @@ Delete the controller device associated with a remote I/O controller including any active association and open queues. .Ss reconnect Reestablish an association for the remote I/O controller associated with -.Ar device-id -at -.Ar address . -The address must include a port. -The flags have the same meaning for the new association as described above +.Ar device-id . +If an +.Ar address +is not provided, +the resolved address and settings from the previous association are used +to establish a new association. +If an +.Ar address +is provided, +the supplied address and command line flags are used to establish a new +association. +In this case, +the address must include a port and +the flags have the same meaning for the new association as described above for the .Cm connect command. diff --git a/sbin/nvmecontrol/reconnect.c b/sbin/nvmecontrol/reconnect.c index 4c9277bd34cb..adf1edac662b 100644 --- a/sbin/nvmecontrol/reconnect.c +++ b/sbin/nvmecontrol/reconnect.c @@ -5,6 +5,7 @@ * Written by: John Baldwin <jhb@FreeBSD.org> */ +#include <sys/dnv.h> #include <sys/nv.h> #include <sys/socket.h> #include <err.h> @@ -24,7 +25,6 @@ static struct options { const char *dev; const char *transport; - const char *address; const char *hostnqn; uint32_t kato; uint16_t num_io_queues; @@ -35,7 +35,6 @@ static struct options { } opt = { .dev = NULL, .transport = "tcp", - .address = NULL, .hostnqn = NULL, .kato = NVMF_KATO_DEFAULT / 1000, .num_io_queues = 1, @@ -46,100 +45,240 @@ static struct options { }; static void -tcp_association_params(struct nvmf_association_params *params) +tcp_association_params(struct nvmf_association_params *params, + bool header_digests, bool data_digests) { params->tcp.pda = 0; - params->tcp.header_digests = opt.header_digests; - params->tcp.data_digests = opt.data_digests; + params->tcp.header_digests = header_digests; + params->tcp.data_digests = data_digests; /* XXX */ params->tcp.maxr2t = 1; } static int -reconnect_nvm_controller(int fd, enum nvmf_trtype trtype, int adrfam, - const char *address, const char *port) +reconnect_nvm_controller(int fd, const struct nvmf_association_params *aparams, + enum nvmf_trtype trtype, int adrfam, const char *address, const char *port, + uint16_t cntlid, const char *subnqn, const char *hostnqn, uint32_t kato, + u_int num_io_queues, u_int queue_size, + const struct nvme_discovery_log_entry *dle) { struct nvme_controller_data cdata; - struct nvmf_association_params aparams; - nvlist_t *rparams; + struct nvme_discovery_log_entry dle_thunk; struct nvmf_qpair *admin, **io; int error; - error = nvmf_reconnect_params(fd, &rparams); + io = calloc(num_io_queues, sizeof(*io)); + error = connect_nvm_queues(aparams, trtype, adrfam, address, port, + cntlid, subnqn, hostnqn, kato, &admin, io, num_io_queues, + queue_size, &cdata); if (error != 0) { - warnc(error, "Failed to fetch reconnect parameters"); - return (EX_IOERR); + free(io); + return (error); } - if (!nvlist_exists_number(rparams, "cntlid") || - !nvlist_exists_string(rparams, "subnqn")) { - nvlist_destroy(rparams); - warnx("Missing required reconnect parameters"); + if (dle == NULL) { + error = nvmf_init_dle_from_admin_qp(admin, &cdata, &dle_thunk); + if (error != 0) { + warnc(error, "Failed to generate handoff parameters"); + disconnect_nvm_queues(admin, io, num_io_queues); + free(io); + return (EX_IOERR); + } + dle = &dle_thunk; + } + + error = nvmf_reconnect_host(fd, dle, hostnqn, admin, num_io_queues, io, + &cdata); + if (error != 0) { + warnc(error, "Failed to handoff queues to kernel"); + free(io); return (EX_IOERR); } + free(io); + return (0); +} + +static int +reconnect_by_address(int fd, const nvlist_t *rparams, const char *addr) +{ + const struct nvme_discovery_log_entry *dle; + struct nvmf_association_params aparams; + enum nvmf_trtype trtype; + const char *address, *hostnqn, *port; + char *subnqn, *tofree; + int error; memset(&aparams, 0, sizeof(aparams)); aparams.sq_flow_control = opt.flow_control; - switch (trtype) { + if (strcasecmp(opt.transport, "tcp") == 0) { + trtype = NVMF_TRTYPE_TCP; + tcp_association_params(&aparams, opt.header_digests, + opt.data_digests); + } else { + warnx("Unsupported or invalid transport"); + return (EX_USAGE); + } + + nvmf_parse_address(addr, &address, &port, &tofree); + if (port == NULL) { + free(tofree); + warnx("Explicit port required"); + return (EX_USAGE); + } + + dle = nvlist_get_binary(rparams, "dle", NULL); + + hostnqn = opt.hostnqn; + if (hostnqn == NULL) + hostnqn = nvmf_default_hostnqn(); + + /* Ensure subnqn is a terminated C string. */ + subnqn = strndup(dle->subnqn, sizeof(dle->subnqn)); + + error = reconnect_nvm_controller(fd, &aparams, trtype, AF_UNSPEC, + address, port, le16toh(dle->cntlid), subnqn, hostnqn, + opt.kato * 1000, opt.num_io_queues, opt.queue_size, NULL); + free(subnqn); + free(tofree); + return (error); +} + +static int +reconnect_by_params(int fd, const nvlist_t *rparams) +{ + struct nvmf_association_params aparams; + const struct nvme_discovery_log_entry *dle; + char *address, *port, *subnqn; + int adrfam, error; + + dle = nvlist_get_binary(rparams, "dle", NULL); + + memset(&aparams, 0, sizeof(aparams)); + aparams.sq_flow_control = nvlist_get_bool(rparams, "sq_flow_control"); + switch (dle->trtype) { case NVMF_TRTYPE_TCP: - tcp_association_params(&aparams); + switch (dle->adrfam) { + case NVMF_ADRFAM_IPV4: + adrfam = AF_INET; + break; + case NVMF_ADRFAM_IPV6: + adrfam = AF_INET6; + break; + default: + warnx("Unsupported address family"); + return (EX_UNAVAILABLE); + } + switch (dle->tsas.tcp.sectype) { + case NVME_TCP_SECURITY_NONE: + break; + default: + warnx("Unsupported TCP security type"); + return (EX_UNAVAILABLE); + } + break; + + tcp_association_params(&aparams, + nvlist_get_bool(rparams, "header_digests"), + nvlist_get_bool(rparams, "data_digests")); break; default: - nvlist_destroy(rparams); - warnx("Unsupported transport %s", nvmf_transport_type(trtype)); + warnx("Unsupported transport %s", + nvmf_transport_type(dle->trtype)); return (EX_UNAVAILABLE); } - io = calloc(opt.num_io_queues, sizeof(*io)); - error = connect_nvm_queues(&aparams, trtype, adrfam, address, port, - nvlist_get_number(rparams, "cntlid"), - nvlist_get_string(rparams, "subnqn"), opt.hostnqn, opt.kato, - &admin, io, opt.num_io_queues, opt.queue_size, &cdata); + /* Ensure address, port, and subnqn is a terminated C string. */ + address = strndup(dle->traddr, sizeof(dle->traddr)); + port = strndup(dle->trsvcid, sizeof(dle->trsvcid)); + subnqn = strndup(dle->subnqn, sizeof(dle->subnqn)); + + error = reconnect_nvm_controller(fd, &aparams, dle->trtype, adrfam, + address, port, le16toh(dle->cntlid), dle->subnqn, + nvlist_get_string(rparams, "hostnqn"), + dnvlist_get_number(rparams, "kato", 0), + nvlist_get_number(rparams, "num_io_queues"), + nvlist_get_number(rparams, "io_qsize"), dle); + free(subnqn); + free(port); + free(address); + return (error); +} + +static int +fetch_and_validate_rparams(int fd, nvlist_t **rparamsp) +{ + const struct nvme_discovery_log_entry *dle; + nvlist_t *rparams; + size_t len; + int error; + + error = nvmf_reconnect_params(fd, &rparams); if (error != 0) { - free(io); + warnc(error, "Failed to fetch reconnect parameters"); + return (EX_IOERR); + } + + if (!nvlist_exists_binary(rparams, "dle") || + !nvlist_exists_string(rparams, "hostnqn") || + !nvlist_exists_number(rparams, "num_io_queues") || + !nvlist_exists_number(rparams, "io_qsize") || + !nvlist_exists_bool(rparams, "sq_flow_control")) { nvlist_destroy(rparams); - return (error); + warnx("Missing required reconnect parameters"); + return (EX_IOERR); } - nvlist_destroy(rparams); - error = nvmf_reconnect_host(fd, admin, opt.num_io_queues, io, &cdata); - if (error != 0) { - warnc(error, "Failed to handoff queues to kernel"); - free(io); + dle = nvlist_get_binary(rparams, "dle", &len); + if (len != sizeof(*dle)) { + nvlist_destroy(rparams); + warnx("Discovery Log entry reconnect parameter is wrong size"); return (EX_IOERR); } - free(io); + + switch (dle->trtype) { + case NVMF_TRTYPE_TCP: + if (!nvlist_exists_bool(rparams, "header_digests") || + !nvlist_exists_bool(rparams, "data_digests")) { + nvlist_destroy(rparams); + warnx("Missing required reconnect parameters"); + return (EX_IOERR); + } + break; + default: + nvlist_destroy(rparams); + warnx("Unsupported transport %s", + nvmf_transport_type(dle->trtype)); + return (EX_UNAVAILABLE); + } + + *rparamsp = rparams; return (0); } static void reconnect_fn(const struct cmd *f, int argc, char *argv[]) { - enum nvmf_trtype trtype; - const char *address, *port; - char *tofree; + nvlist_t *rparams; int error, fd; if (arg_parse(argc, argv, f)) return; - if (strcasecmp(opt.transport, "tcp") == 0) { - trtype = NVMF_TRTYPE_TCP; - } else - errx(EX_USAGE, "Unsupported or invalid transport"); - - nvmf_parse_address(opt.address, &address, &port, &tofree); - open_dev(opt.dev, &fd, 1, 1); - if (port == NULL) - errx(EX_USAGE, "Explicit port required"); + error = fetch_and_validate_rparams(fd, &rparams); + if (error != 0) + exit(error); - error = reconnect_nvm_controller(fd, trtype, AF_UNSPEC, address, port); + /* Check for optional address. */ + if (optind < argc) + error = reconnect_by_address(fd, rparams, argv[optind]); + else + error = reconnect_by_params(fd, rparams); if (error != 0) exit(error); + nvlist_destroy(rparams); close(fd); - free(tofree); } static const struct opts reconnect_opts[] = { @@ -166,7 +305,6 @@ static const struct opts reconnect_opts[] = { static const struct args reconnect_args[] = { { arg_string, &opt.dev, "controller-id" }, - { arg_string, &opt.address, "address" }, { arg_none, NULL, NULL }, }; diff --git a/sys/dev/nvmf/host/nvmf.c b/sys/dev/nvmf/host/nvmf.c index 77d3081243f6..94205666accf 100644 --- a/sys/dev/nvmf/host/nvmf.c +++ b/sys/dev/nvmf/host/nvmf.c @@ -200,8 +200,10 @@ nvmf_send_keep_alive(void *arg) int nvmf_copyin_handoff(const struct nvmf_ioc_nv *nv, nvlist_t **nvlp) { + const struct nvme_discovery_log_entry *dle; + const struct nvme_controller_data *cdata; const nvlist_t *const *io; - const nvlist_t *admin; + const nvlist_t *admin, *rparams; nvlist_t *nvl; size_t i, num_io_queues; uint32_t qsize; @@ -214,7 +216,15 @@ nvmf_copyin_handoff(const struct nvmf_ioc_nv *nv, nvlist_t **nvlp) if (!nvlist_exists_number(nvl, "trtype") || !nvlist_exists_nvlist(nvl, "admin") || !nvlist_exists_nvlist_array(nvl, "io") || - !nvlist_exists_binary(nvl, "cdata")) + !nvlist_exists_binary(nvl, "cdata") || + !nvlist_exists_nvlist(nvl, "rparams")) + goto invalid; + + rparams = nvlist_get_nvlist(nvl, "rparams"); + if (!nvlist_exists_binary(rparams, "dle") || + !nvlist_exists_string(rparams, "hostnqn") || + !nvlist_exists_number(rparams, "num_io_queues") || + !nvlist_exists_number(rparams, "io_qsize")) goto invalid; admin = nvlist_get_nvlist(nvl, "admin"); @@ -224,7 +234,8 @@ nvmf_copyin_handoff(const struct nvmf_ioc_nv *nv, nvlist_t **nvlp) goto invalid; io = nvlist_get_nvlist_array(nvl, "io", &num_io_queues); - if (num_io_queues < 1) + if (num_io_queues < 1 || + num_io_queues != nvlist_get_number(rparams, "num_io_queues")) goto invalid; for (i = 0; i < num_io_queues; i++) { if (!nvmf_validate_qpair_nvlist(io[i], false)) @@ -232,14 +243,20 @@ nvmf_copyin_handoff(const struct nvmf_ioc_nv *nv, nvlist_t **nvlp) } /* Require all I/O queues to be the same size. */ - qsize = nvlist_get_number(io[0], "qsize"); - for (i = 1; i < num_io_queues; i++) { + qsize = nvlist_get_number(rparams, "io_qsize"); + for (i = 0; i < num_io_queues; i++) { if (nvlist_get_number(io[i], "qsize") != qsize) goto invalid; } - nvlist_get_binary(nvl, "cdata", &i); - if (i != sizeof(struct nvme_controller_data)) + cdata = nvlist_get_binary(nvl, "cdata", &i); + if (i != sizeof(*cdata)) + goto invalid; + dle = nvlist_get_binary(rparams, "dle", &i); + if (i != sizeof(*dle)) + goto invalid; + + if (memcmp(dle->subnqn, cdata->subnqn, sizeof(cdata->subnqn)) != 0) goto invalid; *nvlp = nvl; @@ -264,7 +281,7 @@ nvmf_probe(device_t dev) } static int -nvmf_establish_connection(struct nvmf_softc *sc, const nvlist_t *nvl) +nvmf_establish_connection(struct nvmf_softc *sc, nvlist_t *nvl) { const nvlist_t *const *io; const nvlist_t *admin; @@ -294,7 +311,7 @@ nvmf_establish_connection(struct nvmf_softc *sc, const nvlist_t *nvl) sc->io[i] = nvmf_init_qp(sc, trtype, io[i], name, i); if (sc->io[i] == NULL) { device_printf(sc->dev, "Failed to setup I/O queue %u\n", - i + 1); + i); return (ENXIO); } } @@ -314,6 +331,10 @@ nvmf_establish_connection(struct nvmf_softc *sc, const nvlist_t *nvl) memcpy(sc->cdata, nvlist_get_binary(nvl, "cdata", NULL), sizeof(*sc->cdata)); + /* Save reconnect parameters. */ + nvlist_destroy(sc->rparams); + sc->rparams = nvlist_take_nvlist(nvl, "rparams"); + return (0); } @@ -467,7 +488,7 @@ nvmf_attach(device_t dev) { struct make_dev_args mda; struct nvmf_softc *sc = device_get_softc(dev); - const nvlist_t *nvl = device_get_ivars(dev); + nvlist_t *nvl = device_get_ivars(dev); const nvlist_t * const *io; struct sysctl_oid *oid; uint64_t val; @@ -584,6 +605,7 @@ out: taskqueue_drain(taskqueue_thread, &sc->disconnect_task); sx_destroy(&sc->connection_lock); + nvlist_destroy(sc->rparams); free(sc->cdata, M_NVMF); return (error); } @@ -837,6 +859,7 @@ nvmf_detach(device_t dev) nvmf_destroy_aer(sc); sx_destroy(&sc->connection_lock); + nvlist_destroy(sc->rparams); free(sc->cdata, M_NVMF); return (0); } @@ -1053,21 +1076,12 @@ error: static int *** 64 LINES SKIPPED ***