HardenedBSD/usr.sbin/nvmfd/nvmfd.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

261 lines
4.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/param.h>
#include <sys/event.h>
#include <sys/linker.h>
#include <sys/module.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <libnvmf.h>
#include <libutil.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "internal.h"
bool data_digests = false;
bool header_digests = false;
bool flow_control_disable = false;
bool kernel_io = false;
static const char *subnqn;
static volatile bool quit = false;
static void
usage(void)
{
fprintf(stderr, "nvmfd -K [-FGg] [-P port] [-p port] [-t transport] [-n subnqn]\n"
"nvmfd [-dDFH] [-P port] [-p port] [-t transport] [-n subnqn]\n"
"\tdevice [device [...]]\n"
"\n"
"Devices use one of the following syntaxes:\n"
"\tpathame - file or disk device\n"
"\tramdisk:size - memory disk of given size\n");
exit(1);
}
static void
handle_sig(int sig __unused)
{
quit = true;
}
static void
register_listen_socket(int kqfd, int s, void *udata)
{
struct kevent kev;
if (listen(s, -1) != 0)
err(1, "listen");
EV_SET(&kev, s, EVFILT_READ, EV_ADD, 0, 0, udata);
if (kevent(kqfd, &kev, 1, NULL, 0, NULL) == -1)
err(1, "kevent: failed to add listen socket");
}
static void
create_passive_sockets(int kqfd, const char *port, bool discovery)
{
struct addrinfo hints, *ai, *list;
bool created;
int error, s;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
hints.ai_protocol = IPPROTO_TCP;
error = getaddrinfo(NULL, port, &hints, &list);
if (error != 0)
errx(1, "%s", gai_strerror(error));
created = false;
for (ai = list; ai != NULL; ai = ai->ai_next) {
s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (s == -1)
continue;
if (bind(s, ai->ai_addr, ai->ai_addrlen) != 0) {
close(s);
continue;
}
if (discovery) {
register_listen_socket(kqfd, s, (void *)1);
} else {
register_listen_socket(kqfd, s, (void *)2);
discovery_add_io_controller(s, subnqn);
}
created = true;
}
freeaddrinfo(list);
if (!created)
err(1, "Failed to create any listen sockets");
}
static void
handle_connections(int kqfd)
{
struct kevent ev;
int s;
signal(SIGHUP, handle_sig);
signal(SIGINT, handle_sig);
signal(SIGQUIT, handle_sig);
signal(SIGTERM, handle_sig);
while (!quit) {
if (kevent(kqfd, NULL, 0, &ev, 1, NULL) == -1) {
if (errno == EINTR)
continue;
err(1, "kevent");
}
assert(ev.filter == EVFILT_READ);
s = accept(ev.ident, NULL, NULL);
if (s == -1) {
warn("accept");
continue;
}
switch ((uintptr_t)ev.udata) {
case 1:
handle_discovery_socket(s);
break;
case 2:
handle_io_socket(s);
break;
default:
__builtin_unreachable();
}
}
}
int
main(int ac, char **av)
{
struct pidfh *pfh;
const char *dport, *ioport, *transport;
pid_t pid;
int ch, error, kqfd;
bool daemonize;
static char nqn[NVMF_NQN_MAX_LEN];
/* 7.4.9.3 Default port for discovery */
dport = "8009";
pfh = NULL;
daemonize = true;
ioport = "0";
subnqn = NULL;
transport = "tcp";
while ((ch = getopt(ac, av, "dFgGKn:P:p:t:")) != -1) {
switch (ch) {
case 'd':
daemonize = false;
break;
case 'F':
flow_control_disable = true;
break;
case 'G':
data_digests = true;
break;
case 'g':
header_digests = true;
break;
case 'K':
kernel_io = true;
break;
case 'n':
subnqn = optarg;
break;
case 'P':
dport = optarg;
break;
case 'p':
ioport = optarg;
break;
case 't':
transport = optarg;
break;
default:
usage();
}
}
av += optind;
ac -= optind;
if (kernel_io) {
if (ac > 0)
usage();
if (modfind("nvmft") == -1 && kldload("nvmft") == -1)
warn("couldn't load nvmft");
} else {
if (ac < 1)
usage();
}
if (strcasecmp(transport, "tcp") == 0) {
} else
errx(1, "Invalid transport %s", transport);
if (subnqn == NULL) {
error = nvmf_nqn_from_hostuuid(nqn);
if (error != 0)
errc(1, error, "Failed to generate NQN");
subnqn = nqn;
}
if (!kernel_io)
register_devices(ac, av);
init_discovery();
init_io(subnqn);
if (daemonize) {
pfh = pidfile_open(NULL, 0600, &pid);
if (pfh == NULL) {
if (errno == EEXIST)
errx(1, "Daemon already running, pid: %jd",
(intmax_t)pid);
warn("Cannot open or create pidfile");
}
if (daemon(0, 0) != 0) {
pidfile_remove(pfh);
err(1, "Failed to fork into the background");
}
pidfile_write(pfh);
}
kqfd = kqueue();
if (kqfd == -1) {
pidfile_remove(pfh);
err(1, "kqueue");
}
create_passive_sockets(kqfd, dport, true);
create_passive_sockets(kqfd, ioport, false);
handle_connections(kqfd);
shutdown_io();
if (pfh != NULL)
pidfile_remove(pfh);
return (0);
}