mirror of
https://git.hardenedbsd.org/hardenedbsd/HardenedBSD.git
synced 2024-11-23 06:11:06 +01:00
521 lines
12 KiB
C
521 lines
12 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 <err.h>
|
||
|
#include <libnvmf.h>
|
||
|
#include <netdb.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <sysexits.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
#include "fabrics.h"
|
||
|
|
||
|
/*
|
||
|
* Subroutines shared by several Fabrics commands.
|
||
|
*/
|
||
|
static char nqn[NVMF_NQN_MAX_LEN];
|
||
|
static uint8_t hostid[16];
|
||
|
static bool hostid_initted = false;
|
||
|
|
||
|
static bool
|
||
|
init_hostid(void)
|
||
|
{
|
||
|
int error;
|
||
|
|
||
|
if (hostid_initted)
|
||
|
return (true);
|
||
|
|
||
|
error = nvmf_hostid_from_hostuuid(hostid);
|
||
|
if (error != 0) {
|
||
|
warnc(error, "Failed to generate hostid");
|
||
|
return (false);
|
||
|
}
|
||
|
error = nvmf_nqn_from_hostuuid(nqn);
|
||
|
if (error != 0) {
|
||
|
warnc(error, "Failed to generate host NQN");
|
||
|
return (false);
|
||
|
}
|
||
|
|
||
|
hostid_initted = true;
|
||
|
return (true);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nvmf_parse_address(const char *in_address, const char **address,
|
||
|
const char **port, char **tofree)
|
||
|
{
|
||
|
char *cp;
|
||
|
|
||
|
/*
|
||
|
* Accepts the following address formats:
|
||
|
*
|
||
|
* [IPv6 address]:port
|
||
|
* IPv4 address:port
|
||
|
* hostname:port
|
||
|
* [IPv6 address]
|
||
|
* IPv6 address
|
||
|
* IPv4 address
|
||
|
* hostname
|
||
|
*/
|
||
|
if (in_address[0] == '[') {
|
||
|
/* IPv6 address in square brackets. */
|
||
|
cp = strchr(in_address + 1, ']');
|
||
|
if (cp == NULL || cp == in_address + 1)
|
||
|
errx(EX_USAGE, "Invalid address %s", in_address);
|
||
|
*tofree = strndup(in_address + 1, cp - (in_address + 1));
|
||
|
*address = *tofree;
|
||
|
|
||
|
/* Skip over ']' */
|
||
|
cp++;
|
||
|
switch (*cp) {
|
||
|
case '\0':
|
||
|
*port = NULL;
|
||
|
return;
|
||
|
case ':':
|
||
|
if (cp[1] != '\0') {
|
||
|
*port = cp + 1;
|
||
|
return;
|
||
|
}
|
||
|
/* FALLTHROUGH */
|
||
|
default:
|
||
|
errx(EX_USAGE, "Invalid address %s", in_address);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Look for the first colon. */
|
||
|
cp = strchr(in_address, ':');
|
||
|
if (cp == NULL) {
|
||
|
*address = in_address;
|
||
|
*port = NULL;
|
||
|
*tofree = NULL;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* If there is another colon, assume this is an IPv6 address. */
|
||
|
if (strchr(cp + 1, ':') != NULL) {
|
||
|
*address = in_address;
|
||
|
*port = NULL;
|
||
|
*tofree = NULL;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Both strings on either side of the colon must be non-empty. */
|
||
|
if (cp == in_address || cp[1] == '\0')
|
||
|
errx(EX_USAGE, "Invalid address %s", in_address);
|
||
|
|
||
|
*tofree = strndup(in_address, cp - in_address);
|
||
|
*address = *tofree;
|
||
|
|
||
|
/* Skip over ':' */
|
||
|
*port = cp + 1;
|
||
|
}
|
||
|
|
||
|
uint16_t
|
||
|
nvmf_parse_cntlid(const char *cntlid)
|
||
|
{
|
||
|
u_long value;
|
||
|
|
||
|
if (strcasecmp(cntlid, "dynamic") == 0)
|
||
|
return (NVMF_CNTLID_DYNAMIC);
|
||
|
else if (strcasecmp(cntlid, "static") == 0)
|
||
|
return (NVMF_CNTLID_STATIC_ANY);
|
||
|
else {
|
||
|
value = strtoul(cntlid, NULL, 0);
|
||
|
|
||
|
if (value > NVMF_CNTLID_STATIC_MAX)
|
||
|
errx(EX_USAGE, "Invalid controller ID");
|
||
|
|
||
|
return (value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
tcp_qpair_params(struct nvmf_qpair_params *params, int adrfam,
|
||
|
const char *address, const char *port)
|
||
|
{
|
||
|
struct addrinfo hints, *ai, *list;
|
||
|
int error, s;
|
||
|
|
||
|
memset(&hints, 0, sizeof(hints));
|
||
|
hints.ai_family = adrfam;
|
||
|
hints.ai_protocol = IPPROTO_TCP;
|
||
|
error = getaddrinfo(address, port, &hints, &list);
|
||
|
if (error != 0) {
|
||
|
warnx("%s", gai_strerror(error));
|
||
|
return (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 (connect(s, ai->ai_addr, ai->ai_addrlen) != 0) {
|
||
|
close(s);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
params->tcp.fd = s;
|
||
|
freeaddrinfo(list);
|
||
|
return (true);
|
||
|
}
|
||
|
warn("Failed to connect to controller at %s:%s", address, port);
|
||
|
return (false);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
tcp_discovery_association_params(struct nvmf_association_params *params)
|
||
|
{
|
||
|
params->tcp.pda = 0;
|
||
|
params->tcp.header_digests = false;
|
||
|
params->tcp.data_digests = false;
|
||
|
params->tcp.maxr2t = 1;
|
||
|
}
|
||
|
|
||
|
struct nvmf_qpair *
|
||
|
connect_discovery_adminq(enum nvmf_trtype trtype, const char *address,
|
||
|
const char *port, const char *hostnqn)
|
||
|
{
|
||
|
struct nvmf_association_params aparams;
|
||
|
struct nvmf_qpair_params qparams;
|
||
|
struct nvmf_association *na;
|
||
|
struct nvmf_qpair *qp;
|
||
|
uint64_t cap, cc, csts;
|
||
|
int error, timo;
|
||
|
|
||
|
memset(&aparams, 0, sizeof(aparams));
|
||
|
aparams.sq_flow_control = false;
|
||
|
switch (trtype) {
|
||
|
case NVMF_TRTYPE_TCP:
|
||
|
/* 7.4.9.3 Default port for discovery */
|
||
|
if (port == NULL)
|
||
|
port = "8009";
|
||
|
tcp_discovery_association_params(&aparams);
|
||
|
break;
|
||
|
default:
|
||
|
errx(EX_UNAVAILABLE, "Unsupported transport %s",
|
||
|
nvmf_transport_type(trtype));
|
||
|
}
|
||
|
|
||
|
if (!init_hostid())
|
||
|
exit(EX_IOERR);
|
||
|
if (hostnqn != NULL) {
|
||
|
if (!nvmf_nqn_valid(hostnqn))
|
||
|
errx(EX_USAGE, "Invalid HostNQN %s", hostnqn);
|
||
|
} else
|
||
|
hostnqn = nqn;
|
||
|
|
||
|
na = nvmf_allocate_association(trtype, false, &aparams);
|
||
|
if (na == NULL)
|
||
|
err(EX_IOERR, "Failed to create discovery association");
|
||
|
memset(&qparams, 0, sizeof(qparams));
|
||
|
qparams.admin = true;
|
||
|
if (!tcp_qpair_params(&qparams, AF_UNSPEC, address, port))
|
||
|
exit(EX_NOHOST);
|
||
|
qp = nvmf_connect(na, &qparams, 0, NVME_MIN_ADMIN_ENTRIES, hostid,
|
||
|
NVMF_CNTLID_DYNAMIC, NVMF_DISCOVERY_NQN, hostnqn, 0);
|
||
|
if (qp == NULL)
|
||
|
errx(EX_IOERR, "Failed to connect to discovery controller: %s",
|
||
|
nvmf_association_error(na));
|
||
|
nvmf_free_association(na);
|
||
|
|
||
|
/* Fetch Controller Capabilities Property */
|
||
|
error = nvmf_read_property(qp, NVMF_PROP_CAP, 8, &cap);
|
||
|
if (error != 0)
|
||
|
errc(EX_IOERR, error, "Failed to fetch CAP");
|
||
|
|
||
|
/* Set Controller Configuration Property (CC.EN=1) */
|
||
|
error = nvmf_read_property(qp, NVMF_PROP_CC, 4, &cc);
|
||
|
if (error != 0)
|
||
|
errc(EX_IOERR, error, "Failed to fetch CC");
|
||
|
|
||
|
/* Clear known fields preserving any reserved fields. */
|
||
|
cc &= ~(NVMEM(NVME_CC_REG_SHN) | NVMEM(NVME_CC_REG_AMS) |
|
||
|
NVMEM(NVME_CC_REG_MPS) | NVMEM(NVME_CC_REG_CSS));
|
||
|
|
||
|
/* Leave AMS, MPS, and CSS as 0. */
|
||
|
|
||
|
cc |= NVMEF(NVME_CC_REG_EN, 1);
|
||
|
|
||
|
error = nvmf_write_property(qp, NVMF_PROP_CC, 4, cc);
|
||
|
if (error != 0)
|
||
|
errc(EX_IOERR, error, "Failed to set CC");
|
||
|
|
||
|
/* Wait for CSTS.RDY in Controller Status */
|
||
|
timo = NVME_CAP_LO_TO(cap);
|
||
|
for (;;) {
|
||
|
error = nvmf_read_property(qp, NVMF_PROP_CSTS, 4, &csts);
|
||
|
if (error != 0)
|
||
|
errc(EX_IOERR, error, "Failed to fetch CSTS");
|
||
|
|
||
|
if (NVMEV(NVME_CSTS_REG_RDY, csts) != 0)
|
||
|
break;
|
||
|
|
||
|
if (timo == 0)
|
||
|
errx(EX_IOERR, "Controller failed to become ready");
|
||
|
timo--;
|
||
|
usleep(500 * 1000);
|
||
|
}
|
||
|
|
||
|
return (qp);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* XXX: Should this accept the admin queue size as a parameter rather
|
||
|
* than always using NVMF_MIN_ADMIN_MAX_SQ_SIZE?
|
||
|
*/
|
||
|
static int
|
||
|
connect_nvm_adminq(struct nvmf_association *na,
|
||
|
const struct nvmf_qpair_params *params, struct nvmf_qpair **qpp,
|
||
|
uint16_t cntlid, const char *subnqn, const char *hostnqn, uint32_t kato,
|
||
|
uint16_t *mqes)
|
||
|
{
|
||
|
struct nvmf_qpair *qp;
|
||
|
uint64_t cap, cc, csts;
|
||
|
u_int mps, mpsmin, mpsmax;
|
||
|
int error, timo;
|
||
|
|
||
|
qp = nvmf_connect(na, params, 0, NVMF_MIN_ADMIN_MAX_SQ_SIZE, hostid,
|
||
|
cntlid, subnqn, hostnqn, kato);
|
||
|
if (qp == NULL) {
|
||
|
warnx("Failed to connect to NVM controller %s: %s", subnqn,
|
||
|
nvmf_association_error(na));
|
||
|
return (EX_IOERR);
|
||
|
}
|
||
|
|
||
|
/* Fetch Controller Capabilities Property */
|
||
|
error = nvmf_read_property(qp, NVMF_PROP_CAP, 8, &cap);
|
||
|
if (error != 0) {
|
||
|
warnc(error, "Failed to fetch CAP");
|
||
|
nvmf_free_qpair(qp);
|
||
|
return (EX_IOERR);
|
||
|
}
|
||
|
|
||
|
/* Require the NVM command set. */
|
||
|
if (NVME_CAP_HI_CSS_NVM(cap >> 32) == 0) {
|
||
|
warnx("Controller %s does not support the NVM command set",
|
||
|
subnqn);
|
||
|
nvmf_free_qpair(qp);
|
||
|
return (EX_UNAVAILABLE);
|
||
|
}
|
||
|
|
||
|
*mqes = NVME_CAP_LO_MQES(cap);
|
||
|
|
||
|
/* Prefer native host page size if it fits. */
|
||
|
mpsmin = NVMEV(NVME_CAP_HI_REG_MPSMIN, cap >> 32);
|
||
|
mpsmax = NVMEV(NVME_CAP_HI_REG_MPSMAX, cap >> 32);
|
||
|
mps = ffs(getpagesize()) - 1;
|
||
|
if (mps < mpsmin + NVME_MPS_SHIFT)
|
||
|
mps = mpsmin;
|
||
|
else if (mps > mpsmax + NVME_MPS_SHIFT)
|
||
|
mps = mpsmax;
|
||
|
else
|
||
|
mps -= NVME_MPS_SHIFT;
|
||
|
|
||
|
/* Configure controller. */
|
||
|
error = nvmf_read_property(qp, NVMF_PROP_CC, 4, &cc);
|
||
|
if (error != 0) {
|
||
|
warnc(error, "Failed to fetch CC");
|
||
|
nvmf_free_qpair(qp);
|
||
|
return (EX_IOERR);
|
||
|
}
|
||
|
|
||
|
/* Clear known fields preserving any reserved fields. */
|
||
|
cc &= ~(NVMEM(NVME_CC_REG_IOCQES) | NVMEM(NVME_CC_REG_IOSQES) |
|
||
|
NVMEM(NVME_CC_REG_SHN) | NVMEM(NVME_CC_REG_AMS) |
|
||
|
NVMEM(NVME_CC_REG_MPS) | NVMEM(NVME_CC_REG_CSS));
|
||
|
|
||
|
cc |= NVMEF(NVME_CC_REG_IOCQES, 4); /* CQE entry size == 16 */
|
||
|
cc |= NVMEF(NVME_CC_REG_IOSQES, 6); /* SEQ entry size == 64 */
|
||
|
cc |= NVMEF(NVME_CC_REG_AMS, 0); /* AMS 0 (Round-robin) */
|
||
|
cc |= NVMEF(NVME_CC_REG_MPS, mps);
|
||
|
cc |= NVMEF(NVME_CC_REG_CSS, 0); /* NVM command set */
|
||
|
cc |= NVMEF(NVME_CC_REG_EN, 1); /* EN = 1 */
|
||
|
|
||
|
error = nvmf_write_property(qp, NVMF_PROP_CC, 4, cc);
|
||
|
if (error != 0) {
|
||
|
warnc(error, "Failed to set CC");
|
||
|
nvmf_free_qpair(qp);
|
||
|
return (EX_IOERR);
|
||
|
}
|
||
|
|
||
|
/* Wait for CSTS.RDY in Controller Status */
|
||
|
timo = NVME_CAP_LO_TO(cap);
|
||
|
for (;;) {
|
||
|
error = nvmf_read_property(qp, NVMF_PROP_CSTS, 4, &csts);
|
||
|
if (error != 0) {
|
||
|
warnc(error, "Failed to fetch CSTS");
|
||
|
nvmf_free_qpair(qp);
|
||
|
return (EX_IOERR);
|
||
|
}
|
||
|
|
||
|
if (NVMEV(NVME_CSTS_REG_RDY, csts) != 0)
|
||
|
break;
|
||
|
|
||
|
if (timo == 0) {
|
||
|
warnx("Controller failed to become ready");
|
||
|
nvmf_free_qpair(qp);
|
||
|
return (EX_IOERR);
|
||
|
}
|
||
|
timo--;
|
||
|
usleep(500 * 1000);
|
||
|
}
|
||
|
|
||
|
*qpp = qp;
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
shutdown_controller(struct nvmf_qpair *qp)
|
||
|
{
|
||
|
uint64_t cc;
|
||
|
int error;
|
||
|
|
||
|
error = nvmf_read_property(qp, NVMF_PROP_CC, 4, &cc);
|
||
|
if (error != 0) {
|
||
|
warnc(error, "Failed to fetch CC");
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
cc |= NVMEF(NVME_CC_REG_SHN, NVME_SHN_NORMAL);
|
||
|
|
||
|
error = nvmf_write_property(qp, NVMF_PROP_CC, 4, cc);
|
||
|
if (error != 0) {
|
||
|
warnc(error, "Failed to set CC to trigger shutdown");
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
nvmf_free_qpair(qp);
|
||
|
}
|
||
|
|
||
|
/* Returns a value from <sysexits.h> */
|
||
|
int
|
||
|
connect_nvm_queues(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, struct nvmf_qpair **admin, struct nvmf_qpair **io,
|
||
|
u_int num_io_queues, u_int queue_size, struct nvme_controller_data *cdata)
|
||
|
{
|
||
|
struct nvmf_qpair_params qparams;
|
||
|
struct nvmf_association *na;
|
||
|
u_int queues;
|
||
|
int error;
|
||
|
uint16_t mqes;
|
||
|
|
||
|
switch (trtype) {
|
||
|
case NVMF_TRTYPE_TCP:
|
||
|
break;
|
||
|
default:
|
||
|
warnx("Unsupported transport %s", nvmf_transport_type(trtype));
|
||
|
return (EX_UNAVAILABLE);
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
|
||
|
/* Association. */
|
||
|
na = nvmf_allocate_association(trtype, false, aparams);
|
||
|
if (na == NULL) {
|
||
|
warn("Failed to create association for %s", subnqn);
|
||
|
return (EX_IOERR);
|
||
|
}
|
||
|
|
||
|
/* Admin queue. */
|
||
|
memset(&qparams, 0, sizeof(qparams));
|
||
|
qparams.admin = true;
|
||
|
if (!tcp_qpair_params(&qparams, adrfam, address, port)) {
|
||
|
nvmf_free_association(na);
|
||
|
return (EX_NOHOST);
|
||
|
}
|
||
|
error = connect_nvm_adminq(na, &qparams, admin, cntlid, subnqn, hostnqn,
|
||
|
kato, &mqes);
|
||
|
if (error != 0) {
|
||
|
nvmf_free_association(na);
|
||
|
return (error);
|
||
|
}
|
||
|
|
||
|
/* Validate I/O queue size. */
|
||
|
if (queue_size == 0)
|
||
|
queue_size = mqes + 1;
|
||
|
else if (queue_size > mqes + 1) {
|
||
|
shutdown_controller(*admin);
|
||
|
nvmf_free_association(na);
|
||
|
warn("I/O queue size exceeds controller maximum (%u)",
|
||
|
mqes + 1);
|
||
|
return (EX_USAGE);
|
||
|
}
|
||
|
|
||
|
/* Fetch controller data. */
|
||
|
error = nvmf_host_identify_controller(*admin, cdata);
|
||
|
if (error != 0) {
|
||
|
shutdown_controller(*admin);
|
||
|
nvmf_free_association(na);
|
||
|
warnc(error, "Failed to fetch controller data for %s", subnqn);
|
||
|
return (EX_IOERR);
|
||
|
}
|
||
|
|
||
|
nvmf_update_assocation(na, cdata);
|
||
|
|
||
|
error = nvmf_host_request_queues(*admin, num_io_queues, &queues);
|
||
|
if (error != 0) {
|
||
|
shutdown_controller(*admin);
|
||
|
nvmf_free_association(na);
|
||
|
warnc(error, "Failed to request I/O queues");
|
||
|
return (EX_IOERR);
|
||
|
}
|
||
|
if (queues < num_io_queues) {
|
||
|
shutdown_controller(*admin);
|
||
|
nvmf_free_association(na);
|
||
|
warnx("Controller enabled fewer I/O queues (%u) than requested (%u)",
|
||
|
queues, num_io_queues);
|
||
|
return (EX_PROTOCOL);
|
||
|
}
|
||
|
|
||
|
/* I/O queues. */
|
||
|
memset(io, 0, sizeof(io) * num_io_queues);
|
||
|
for (u_int i = 0; i < num_io_queues; i++) {
|
||
|
memset(&qparams, 0, sizeof(qparams));
|
||
|
qparams.admin = false;
|
||
|
if (!tcp_qpair_params(&qparams, adrfam, address, port)) {
|
||
|
error = EX_NOHOST;
|
||
|
goto out;
|
||
|
}
|
||
|
io[i] = nvmf_connect(na, &qparams, i + 1, queue_size, hostid,
|
||
|
nvmf_cntlid(*admin), subnqn, hostnqn, 0);
|
||
|
if (io[i] == NULL) {
|
||
|
warnx("Failed to create I/O queue: %s",
|
||
|
nvmf_association_error(na));
|
||
|
error = EX_IOERR;
|
||
|
goto out;
|
||
|
}
|
||
|
}
|
||
|
nvmf_free_association(na);
|
||
|
return (0);
|
||
|
|
||
|
out:
|
||
|
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);
|
||
|
}
|