freebsd-src/usr.sbin/nvmfd/discovery.c
John Baldwin a8089ea5ae nvmfd: A simple userspace daemon for the NVMe over Fabrics controller
This daemon can operate as a purely userspace controller exporting one
or more simulated RAM disks or local block devices as NVMe namespaces
to a remote host.  In this case the daemon provides a discovery
controller with a single entry for an I/O controller.

nvmfd can also offload I/O controller queue pairs to the nvmft.ko
in-kernel Fabrics controller when -K is passed.  In this mode, nvmfd
still accepts connections and performs initial transport-specific
negotitation in userland.  The daemon still provides a userspace-only
discovery controller with a single entry for an I/O controller.
However, queue pairs for the I/O controller are handed off to the CTL
NVMF frontend.

Eventually ctld(8) should be refactored to to provide an abstraction
for the frontend protocol and the discovery and the kernel mode of
this daemon should be merged into ctld(8).  At that point this daemon
can be moved to tools/tools/nvmf as a debugging tool (mostly as sample
code for a userspace controller using libnvmf).

Reviewed by:	imp
Sponsored by:	Chelsio Communications
Differential Revision:	https://reviews.freebsd.org/D44731
2024-05-02 16:38:39 -07:00

344 lines
7.8 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2023-2024 Chelsio Communications, Inc.
* Written by: John Baldwin <jhb@FreeBSD.org>
*/
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <err.h>
#include <libnvmf.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "internal.h"
struct io_controller_data {
struct nvme_discovery_log_entry entry;
bool wildcard;
};
struct discovery_controller {
struct nvme_discovery_log *discovery_log;
size_t discovery_log_len;
int s;
};
struct discovery_thread_arg {
struct controller *c;
struct nvmf_qpair *qp;
int s;
};
static struct io_controller_data *io_controllers;
static struct nvmf_association *discovery_na;
static u_int num_io_controllers;
static bool
init_discovery_log_entry(struct nvme_discovery_log_entry *entry, int s,
const char *subnqn)
{
struct sockaddr_storage ss;
socklen_t len;
bool wildcard;
len = sizeof(ss);
if (getsockname(s, (struct sockaddr *)&ss, &len) == -1)
err(1, "getsockname");
memset(entry, 0, sizeof(*entry));
entry->trtype = NVMF_TRTYPE_TCP;
switch (ss.ss_family) {
case AF_INET:
{
struct sockaddr_in *sin;
sin = (struct sockaddr_in *)&ss;
entry->adrfam = NVMF_ADRFAM_IPV4;
snprintf(entry->trsvcid, sizeof(entry->trsvcid), "%u",
htons(sin->sin_port));
if (inet_ntop(AF_INET, &sin->sin_addr, entry->traddr,
sizeof(entry->traddr)) == NULL)
err(1, "inet_ntop");
wildcard = (sin->sin_addr.s_addr == htonl(INADDR_ANY));
break;
}
case AF_INET6:
{
struct sockaddr_in6 *sin6;
sin6 = (struct sockaddr_in6 *)&ss;
entry->adrfam = NVMF_ADRFAM_IPV6;
snprintf(entry->trsvcid, sizeof(entry->trsvcid), "%u",
htons(sin6->sin6_port));
if (inet_ntop(AF_INET6, &sin6->sin6_addr, entry->traddr,
sizeof(entry->traddr)) == NULL)
err(1, "inet_ntop");
wildcard = (memcmp(&sin6->sin6_addr, &in6addr_any,
sizeof(in6addr_any)) == 0);
break;
}
default:
errx(1, "Unsupported address family %u", ss.ss_family);
}
entry->subtype = NVMF_SUBTYPE_NVME;
if (flow_control_disable)
entry->treq |= (1 << 2);
entry->portid = htole16(1);
entry->cntlid = htole16(NVMF_CNTLID_DYNAMIC);
entry->aqsz = NVME_MAX_ADMIN_ENTRIES;
strlcpy(entry->subnqn, subnqn, sizeof(entry->subnqn));
return (wildcard);
}
void
init_discovery(void)
{
struct nvmf_association_params aparams;
memset(&aparams, 0, sizeof(aparams));
aparams.sq_flow_control = false;
aparams.dynamic_controller_model = true;
aparams.max_admin_qsize = NVME_MAX_ADMIN_ENTRIES;
aparams.tcp.pda = 0;
aparams.tcp.header_digests = header_digests;
aparams.tcp.data_digests = data_digests;
aparams.tcp.maxr2t = 1;
aparams.tcp.maxh2cdata = 256 * 1024;
discovery_na = nvmf_allocate_association(NVMF_TRTYPE_TCP, true,
&aparams);
if (discovery_na == NULL)
err(1, "Failed to create discovery association");
}
void
discovery_add_io_controller(int s, const char *subnqn)
{
struct io_controller_data *icd;
io_controllers = reallocf(io_controllers, (num_io_controllers + 1) *
sizeof(*io_controllers));
icd = &io_controllers[num_io_controllers];
num_io_controllers++;
icd->wildcard = init_discovery_log_entry(&icd->entry, s, subnqn);
}
static void
build_discovery_log_page(struct discovery_controller *dc)
{
struct sockaddr_storage ss;
socklen_t len;
char traddr[256];
u_int i, nentries;
uint8_t adrfam;
if (dc->discovery_log != NULL)
return;
len = sizeof(ss);
if (getsockname(dc->s, (struct sockaddr *)&ss, &len) == -1) {
warn("build_discovery_log_page: getsockname");
return;
}
memset(traddr, 0, sizeof(traddr));
switch (ss.ss_family) {
case AF_INET:
{
struct sockaddr_in *sin;
sin = (struct sockaddr_in *)&ss;
adrfam = NVMF_ADRFAM_IPV4;
if (inet_ntop(AF_INET, &sin->sin_addr, traddr,
sizeof(traddr)) == NULL) {
warn("build_discovery_log_page: inet_ntop");
return;
}
break;
}
case AF_INET6:
{
struct sockaddr_in6 *sin6;
sin6 = (struct sockaddr_in6 *)&ss;
adrfam = NVMF_ADRFAM_IPV6;
if (inet_ntop(AF_INET6, &sin6->sin6_addr, traddr,
sizeof(traddr)) == NULL) {
warn("build_discovery_log_page: inet_ntop");
return;
}
break;
}
default:
assert(false);
}
nentries = 0;
for (i = 0; i < num_io_controllers; i++) {
if (io_controllers[i].wildcard &&
io_controllers[i].entry.adrfam != adrfam)
continue;
nentries++;
}
dc->discovery_log_len = sizeof(*dc->discovery_log) +
nentries * sizeof(struct nvme_discovery_log_entry);
dc->discovery_log = calloc(dc->discovery_log_len, 1);
dc->discovery_log->numrec = nentries;
dc->discovery_log->recfmt = 0;
nentries = 0;
for (i = 0; i < num_io_controllers; i++) {
if (io_controllers[i].wildcard &&
io_controllers[i].entry.adrfam != adrfam)
continue;
dc->discovery_log->entries[nentries] = io_controllers[i].entry;
if (io_controllers[i].wildcard)
memcpy(dc->discovery_log->entries[nentries].traddr,
traddr, sizeof(traddr));
}
}
static void
handle_get_log_page_command(const struct nvmf_capsule *nc,
const struct nvme_command *cmd, struct discovery_controller *dc)
{
uint64_t offset;
uint32_t length;
switch (nvmf_get_log_page_id(cmd)) {
case NVME_LOG_DISCOVERY:
break;
default:
warnx("Unsupported log page %u for discovery controller",
nvmf_get_log_page_id(cmd));
goto error;
}
build_discovery_log_page(dc);
offset = nvmf_get_log_page_offset(cmd);
if (offset >= dc->discovery_log_len)
goto error;
length = nvmf_get_log_page_length(cmd);
if (length > dc->discovery_log_len - offset)
length = dc->discovery_log_len - offset;
nvmf_send_controller_data(nc, (char *)dc->discovery_log + offset,
length);
return;
error:
nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD);
}
static bool
discovery_command(const struct nvmf_capsule *nc, const struct nvme_command *cmd,
void *arg)
{
struct discovery_controller *dc = arg;
switch (cmd->opc) {
case NVME_OPC_GET_LOG_PAGE:
handle_get_log_page_command(nc, cmd, dc);
return (true);
default:
return (false);
}
}
static void *
discovery_thread(void *arg)
{
struct discovery_thread_arg *dta = arg;
struct discovery_controller dc;
pthread_detach(pthread_self());
memset(&dc, 0, sizeof(dc));
dc.s = dta->s;
controller_handle_admin_commands(dta->c, discovery_command, &dc);
free(dc.discovery_log);
free_controller(dta->c);
nvmf_free_qpair(dta->qp);
close(dta->s);
free(dta);
return (NULL);
}
void
handle_discovery_socket(int s)
{
struct nvmf_fabric_connect_data data;
struct nvme_controller_data cdata;
struct nvmf_qpair_params qparams;
struct discovery_thread_arg *dta;
struct nvmf_capsule *nc;
struct nvmf_qpair *qp;
pthread_t thr;
int error;
memset(&qparams, 0, sizeof(qparams));
qparams.tcp.fd = s;
nc = NULL;
qp = nvmf_accept(discovery_na, &qparams, &nc, &data);
if (qp == NULL) {
warnx("Failed to create discovery qpair: %s",
nvmf_association_error(discovery_na));
goto error;
}
if (strcmp(data.subnqn, NVMF_DISCOVERY_NQN) != 0) {
warn("Discovery qpair with invalid SubNQN: %.*s",
(int)sizeof(data.subnqn), data.subnqn);
nvmf_connect_invalid_parameters(nc, true,
offsetof(struct nvmf_fabric_connect_data, subnqn));
goto error;
}
/* Just use a controller ID of 1 for all discovery controllers. */
error = nvmf_finish_accept(nc, 1);
if (error != 0) {
warnc(error, "Failed to send CONNECT reponse");
goto error;
}
nvmf_init_discovery_controller_data(qp, &cdata);
dta = malloc(sizeof(*dta));
dta->qp = qp;
dta->s = s;
dta->c = init_controller(qp, &cdata);
error = pthread_create(&thr, NULL, discovery_thread, dta);
if (error != 0) {
warnc(error, "Failed to create discovery thread");
free_controller(dta->c);
free(dta);
goto error;
}
nvmf_free_capsule(nc);
return;
error:
if (nc != NULL)
nvmf_free_capsule(nc);
if (qp != NULL)
nvmf_free_qpair(qp);
close(s);
}