Page MenuHomeFreeBSD

D48773.id.diff
No OneTemporary

D48773.id.diff

diff --git a/usr.sbin/ctld/Makefile b/usr.sbin/ctld/Makefile
--- a/usr.sbin/ctld/Makefile
+++ b/usr.sbin/ctld/Makefile
@@ -6,17 +6,19 @@
PACKAGE= iscsi
PROG= ctld
SRCS= ctld.c isns.c kernel.c
+SRCS+= nvme.c nvme_discovery.c
SRCS+= parse.y token.l y.tab.h uclparse.c
CFLAGS+= -I${.CURDIR}
CFLAGS+= -I${SRCTOP}/sys
CFLAGS+= -I${SRCTOP}/sys/cam/ctl
CFLAGS+= -I${SRCTOP}/sys/dev/iscsi
CFLAGS+= -I${SRCTOP}/lib/libiscsiutil
+CFLAGS+= -I${SRCTOP}/lib/libnvmf
#CFLAGS+= -DICL_KERNEL_PROXY
NO_WCAST_ALIGN=
MAN= ctld.8 ctl.conf.5
-LIBADD= bsdxml iscsiutil md sbuf util ucl m nv
+LIBADD= bsdxml iscsiutil nvmf md sbuf util ucl m nv
YFLAGS+= -v
CLEANFILES= y.tab.c y.tab.h y.output
diff --git a/usr.sbin/ctld/ctl.conf.5 b/usr.sbin/ctld/ctl.conf.5
--- a/usr.sbin/ctld/ctl.conf.5
+++ b/usr.sbin/ctld/ctl.conf.5
@@ -31,7 +31,7 @@
.Os
.Sh NAME
.Nm ctl.conf
-.Nd CAM Target Layer / iSCSI target daemon configuration file
+.Nd CAM Target Layer / iSCSI target / NVMeoF controller daemon configuration file
.Sh DESCRIPTION
The
.Nm
@@ -59,6 +59,11 @@
.Dl ...
}
+.No transport-group Ar name No {
+.Dl listen Ar transport Ar address
+.Dl ...
+}
+
.No target Ar name {
.Dl auth-group Ar name
.Dl portal-group Ar name
@@ -67,6 +72,15 @@
.Dl }
.Dl ...
}
+
+.No controller Ar name {
+.Dl auth-group Ar name
+.Dl transport-group Ar name
+.Dl namespace Ar number No {
+.Dl path Ar path
+.Dl }
+.Dl ...
+}
.Ed
.Ss Global Context
.Bl -tag -width indent
@@ -94,16 +108,29 @@
configuration context,
defining a new portal-group,
which can then be assigned to any number of targets.
+.It Ic transport-group Ar name
+Create a
+.Sy transport-group
+configuration context,
+defining a new transport-group,
+which can then be assigned to any number of NVMeoF controllers.
.It Ic lun Ar name
Create a
.Sy lun
-configuration context, defining a LUN to be exported by any number of targets.
+configuration context, defining a LUN to be exported by any number of targets
+or controllers.
.It Ic target Ar name
Create a
.Sy target
configuration context, which can optionally contain one or more
.Sy lun
contexts.
+.It Ic controller Ar name
+Create a
+.Sy controller
+configuration context, which can optionally contain one or more
+.Sy namespace
+contexts.
.It Ic timeout Ar seconds
The timeout for login sessions, after which the connection
will be forcibly terminated.
@@ -150,6 +177,19 @@
or
.Sy chap-mutual
entries; it is an error to mix them.
+.It Ic host-address Ar address Ns Op / Ns Ar prefixlen
+An NVMeoF host address: an IPv4 or IPv6 address, optionally
+followed by a literal slash and a prefix length.
+Only NVMeoF hosts with an address matching one of the defined
+addresses will be allowed to connect.
+If not defined, there will be no restrictions based on host
+address.
+.It Ic host-nqn Ar name
+An NVMeoF host name.
+Only NVMeoF hosts with a name matching one of the defined
+names will be allowed to connect.
+If not defined, there will be no restrictions based on NVMe host
+name.
.It Ic initiator-name Ar initiator-name
An iSCSI initiator name.
Only initiators with a name matching one of the defined
@@ -264,6 +304,58 @@
.Qq Ar 7 .
When omitted, the default for the outgoing interface is used.
.El
+.Ss transport-group Context
+.Bl -tag -width indent
+.It Ic discovery-auth-group Ar name
+See the description for this option for
+.Sy portal-group
+contexts.
+.It Ic discovery-filter Ar filter
+Filter can be either
+.Qq Ar none ,
+.Qq Ar address ,
+or
+.Qq Ar address-name .
+When set to
+.Qq Ar none ,
+discovery will return all controllers assigned to that transport group.
+When set to
+.Qq Ar address ,
+discovery will not return controllers that cannot be accessed by the
+host because of their
+.Sy host-address .
+When set to
+.Qq Ar address-name ,
+the check will include both
+.Sy host-address
+and
+.Sy host-nqn .
+The default is
+.Qq Ar none .
+.It Ic listen Ar transport Ar address
+An IPv4 or IPv6 address and port to listen on for incoming connections
+using the specified NVMeoF transport.
+Supported transports are
+.Qq Ar tcp
+.Pq for NVMe/TCP I/O controllers
+and
+.Qq Ar discovery-tcp
+.Pq for NVMe/TCP discovery controllers .
+.It Ic option Ar name Ar value
+The CTL-specific port options passed to the kernel.
+.It Ic tag Ar value
+Unique 16-bit port ID for this
+.Sy transport-group .
+If not specified, the value is generated automatically.
+.It Ic dscp Ar value
+See the description for this option for
+.Sy portal-group
+contexts.
+.It Ic pcp Ar value
+See the description for this option for
+.Sy portal-group
+contexts.
+.El
.Ss target Context
.Bl -tag -width indent
.It Ic alias Ar text
@@ -390,6 +482,101 @@
This is an alternative to defining the LUN separately, useful in the common
case of a LUN being exported by a single target.
.El
+.Ss controller Context
+.Bl -tag -width indent
+.It Ic auth-group Ar name
+Assign a previously defined authentication group to the controller.
+By default, controllers that do not specify their own auth settings,
+using clauses such as
+.Sy host-address
+or
+.Sy host-nqn ,
+are assigned to the
+predefined
+.Sy auth-group
+.Qq Ar default ,
+which denies all access.
+Another predefined
+.Sy auth-group ,
+.Qq Ar no-authentication ,
+may be used to permit access
+without authentication.
+Note that this clause can be overridden using the second argument
+to a
+.Sy transport-group
+clause.
+.It Ic auth-type Ar type
+Sets the authentication type.
+Type can be either
+.Qq Ar none
+or
+.Qq Ar deny .
+In most cases it is not necessary to set the type using this clause;
+it is usually used to disable authentication for a given
+.Sy controller .
+This clause is mutually exclusive with
+.Sy auth-group ;
+one cannot use
+both in a single controller.
+.It Ic host-address Ar address Ns Op / Ns Ar prefixlen
+An NVMeoF host address: an IPv4 or IPv6 address, optionally
+followed by a literal slash and a prefix length.
+Only NVMeoF hosts with an address matching one of the defined
+addresses will be allowed to connect.
+If not defined, there will be no restrictions based on host
+address.
+This clause is mutually exclusive with
+.Sy auth-group ;
+one cannot use
+both in a single controller.
+.It Ic host-nqn Ar name
+An NVMeoF host name.
+Only NVMeoF hosts with a name matching one of the defined
+names will be allowed to connect.
+If not defined, there will be no restrictions based on NVMe host
+name.
+This clause is mutually exclusive with
+.Sy auth-group ;
+one cannot use
+both in a single target.
+.Pp
+The
+.Sy auth-type ,
+.Sy host-address ,
+and
+.Sy host-nqn
+clauses in the controller context provide an alternative to assigning an
+.Sy auth-group
+defined separately, useful in the common case of authentication settings
+specific to a single controller.
+.It Ic transport-group Ar name Op Ar ag-name
+Assign a previously defined transport group to the controller.
+The default transport group is
+.Qq Ar default ,
+which makes the controller available
+on TCP port 4420 on all configured IPv4 and IPv6 addresses.
+The optional second argument specifies the
+.Sy auth-group
+for connections to this specific transport group group.
+If the second argument is not specified, the controller
+.Sy auth-group
+is used.
+.It Ic namespace Ar number Ar name
+Export previously defined
+.Sy lun
+as an NVMe namespace from the parent controller.
+.It Ic namespace Ar number
+Create a
+.Sy namespace
+configuration context, defining an NVMe namespace exported by the parent target.
+.Pp
+This is an alternative to defining the namespace separately,
+useful in the common case of a namespace being exported by a single controller.
+.Sy namespace
+configuration contexts accept the the same properties as
+.Sy lun
+contexts.
+.El
.Ss lun Context
.Bl -tag -width indent
.It Ic backend Ar block No | Ar ramdisk
@@ -410,7 +597,7 @@
By default CTL allocates those IDs dynamically, but explicit specification
may be needed for consistency in HA configurations.
.It Ic device-id Ar string
-The SCSI Device Identification string presented to the initiator.
+The SCSI Device Identification string presented to iSCSI initiators.
.It Ic device-type Ar type
Specify the SCSI device type to use when creating the LUN.
Currently CTL supports Direct Access (type 0), Processor (type 3)
@@ -425,11 +612,11 @@
The path to the file, device node, or
.Xr zfs 8
volume used to back the LUN.
-For optimal performance, create the volume with the
+For optimal performance, create ZFS volumes with the
.Qq Ar volmode=dev
property set.
.It Ic serial Ar string
-The SCSI serial number presented to the initiator.
+The SCSI serial number presented to iSCSI initiators.
.It Ic size Ar size
The LUN size, in bytes or by number with a suffix of
.Sy K , M , G , T
@@ -498,6 +685,16 @@
port isp1
lun 0 example_1
}
+
+controller nqn.2012-06.com.example:controller1 {
+ auth-group no-authentication;
+ namespace 1 example_1
+ namespace 2 {
+ backend ramdisk
+ size 1G
+ option capacity 1G
+ }
+}
.Ed
.Pp
An equivalent configuration in UCL format, for use with
@@ -562,6 +759,14 @@
vendor = "FreeBSD"
}
}
+
+ example_3 {
+ backend = ramdisk
+ size = 1G
+ options {
+ capacity = 1G
+ }
+ }
}
target {
@@ -589,6 +794,17 @@
]
}
}
+
+controller {
+ "nqn.2012-06.com.example:controller1" {
+ auth-group = no-authentication
+ namespace = [
+ { nsid = 1, name = example_1 },
+ { nsid = 2, name = example_3 }
+ ]
+ }
+}
+
.Ed
.Sh SEE ALSO
.Xr ctl 4 ,
diff --git a/usr.sbin/ctld/ctld.h b/usr.sbin/ctld/ctld.h
--- a/usr.sbin/ctld/ctld.h
+++ b/usr.sbin/ctld/ctld.h
@@ -49,6 +49,8 @@
#define MAX_LUNS 1024
struct ctl_req;
+struct nvmf_association;
+struct nvmf_association_params;
struct port;
struct target_protocol_ops;
@@ -94,10 +96,14 @@
TAILQ_HEAD(, auth) ag_auths;
struct auth_name_head ag_initiator_names;
struct auth_portal_head ag_initiator_portals;
+ struct auth_name_head ag_host_names;
+ struct auth_portal_head ag_host_addresses;
};
#define PORTAL_PROTOCOL_ISCSI 0
#define PORTAL_PROTOCOL_ISER 1
+#define PORTAL_PROTOCOL_NVME_TCP 2
+#define PORTAL_PROTOCOL_NVME_DISCOVERY_TCP 3
struct portal {
TAILQ_ENTRY(portal) p_next;
@@ -111,9 +117,17 @@
TAILQ_HEAD(, target) p_targets;
int p_socket;
+
+ union {
+ struct {
+ struct nvmf_association_params *aparams;
+ struct nvmf_association *association;
+ } p_nvme;
+ };
};
#define TARGET_PROTOCOL_ISCSI 0
+#define TARGET_PROTOCOL_NVME 1
struct target_protocol_ops {
/* Initialize protocol-specific state for a new portal group. */
@@ -249,6 +263,7 @@
int conf_debug;
int conf_timeout;
int conf_maxproc;
+ uint32_t conf_genctr;
#ifdef ICL_KERNEL_PROXY
int conf_portal_id;
@@ -256,6 +271,7 @@
struct pidfh *conf_pidfh;
bool conf_default_pg_defined;
+ bool conf_default_tg_defined;
bool conf_default_ag_defined;
bool conf_kernel_port_on;
};
@@ -291,6 +307,7 @@
extern bool proxy_mode;
extern int ctl_fd;
extern struct target_protocol_ops target_iscsi;
+extern struct target_protocol_ops target_nvme;
int parse_conf(struct conf *newconf, const char *path);
int uclparse_conf(struct conf *conf, const char *path);
@@ -349,6 +366,9 @@
int portal_group_set_redirection(struct portal_group *pg,
const char *addr);
+int transport_group_set_filter(struct portal_group *pg,
+ const char *filter);
+
int isns_new(struct conf *conf, const char *addr);
void isns_delete(struct isns *is);
void isns_register(struct isns *isns, struct isns *oldisns);
@@ -426,4 +446,7 @@
void set_timeout(int timeout, int fatal);
+void nvme_handle_discovery_socket(struct portal *p, int s,
+ const struct sockaddr *client_sa);
+
#endif /* !CTLD_H */
diff --git a/usr.sbin/ctld/ctld.c b/usr.sbin/ctld/ctld.c
--- a/usr.sbin/ctld/ctld.c
+++ b/usr.sbin/ctld/ctld.c
@@ -74,6 +74,7 @@
struct conf *
conf_new(void)
{
+ static uint32_t genctr;
struct conf *conf;
conf = calloc(1, sizeof(*conf));
@@ -91,6 +92,7 @@
conf->conf_debug = 0;
conf->conf_timeout = 60;
conf->conf_maxproc = 30;
+ conf->conf_genctr = genctr++;
return (conf);
}
@@ -285,6 +287,8 @@
switch (protocol) {
case TARGET_PROTOCOL_ISCSI:
return (&ag->ag_initiator_names);
+ case TARGET_PROTOCOL_NVME:
+ return (&ag->ag_host_names);
default:
__assert_unreachable();
}
@@ -296,6 +300,8 @@
switch (protocol) {
case TARGET_PROTOCOL_ISCSI:
return (&ag->ag_initiator_names);
+ case TARGET_PROTOCOL_NVME:
+ return (&ag->ag_host_names);
default:
__assert_unreachable();
}
@@ -371,6 +377,8 @@
switch (protocol) {
case TARGET_PROTOCOL_ISCSI:
return (&ag->ag_initiator_portals);
+ case TARGET_PROTOCOL_NVME:
+ return (&ag->ag_host_addresses);
default:
__assert_unreachable();
}
@@ -382,6 +390,8 @@
switch (protocol) {
case TARGET_PROTOCOL_ISCSI:
return (&ag->ag_initiator_portals);
+ case TARGET_PROTOCOL_NVME:
+ return (&ag->ag_host_addresses);
default:
__assert_unreachable();
}
@@ -547,6 +557,8 @@
TAILQ_INIT(&ag->ag_auths);
TAILQ_INIT(&ag->ag_initiator_names);
TAILQ_INIT(&ag->ag_initiator_portals);
+ TAILQ_INIT(&ag->ag_host_names);
+ TAILQ_INIT(&ag->ag_host_addresses);
ag->ag_conf = conf;
TAILQ_INSERT_TAIL(&conf->conf_auth_groups, ag, ag_next);
@@ -570,6 +582,12 @@
TAILQ_FOREACH_SAFE(auth_portal, &ag->ag_initiator_portals, ap_next,
auth_portal_tmp)
auth_portal_delete(auth_portal);
+ TAILQ_FOREACH_SAFE(auth_name, &ag->ag_host_names, an_next,
+ auth_name_tmp)
+ auth_name_delete(auth_name);
+ TAILQ_FOREACH_SAFE(auth_portal, &ag->ag_host_addresses, ap_next,
+ auth_portal_tmp)
+ auth_portal_delete(auth_portal);
free(ag->ag_name);
free(ag);
}
@@ -662,6 +680,8 @@
case TARGET_PROTOCOL_ISCSI:
return (&target_iscsi);
#endif
+ case TARGET_PROTOCOL_NVME:
+ return (&target_nvme);
default:
return (NULL);
}
@@ -673,6 +693,8 @@
switch (protocol) {
case TARGET_PROTOCOL_ISCSI:
return "portal-group";
+ case TARGET_PROTOCOL_NVME:
+ return "transport-group";
default:
__assert_unreachable();
}
@@ -814,6 +836,12 @@
case PORTAL_PROTOCOL_ISCSI:
def_port = "3260";
break;
+ case PORTAL_PROTOCOL_NVME_TCP:
+ def_port = "4420";
+ break;
+ case PORTAL_PROTOCOL_NVME_DISCOVERY_TCP:
+ def_port = "8009";
+ break;
default:
__builtin_unreachable();
}
@@ -1131,6 +1159,38 @@
return (0);
}
+int
+transport_group_set_filter(struct portal_group *pg, const char *str)
+{
+ int filter;
+
+ if (strcmp(str, "none") == 0) {
+ filter = PG_FILTER_NONE;
+ } else if (strcmp(str, "address") == 0) {
+ filter = PG_FILTER_PORTAL;
+ } else if (strcmp(str, "address-name") == 0) {
+ filter = PG_FILTER_PORTAL_NAME;
+ } else {
+ log_warnx("invalid discovery-filter \"%s\" for transport-group "
+ "\"%s\"; valid values are \"none\", \"address\", "
+ "and \"address-name\"",
+ str, pg->pg_name);
+ return (1);
+ }
+
+ if (pg->pg_discovery_filter != PG_FILTER_UNKNOWN &&
+ pg->pg_discovery_filter != filter) {
+ log_warnx("cannot set discovery-filter to \"%s\" for "
+ "transport-group \"%s\"; already has a different "
+ "value", str, pg->pg_name);
+ return (1);
+ }
+
+ pg->pg_discovery_filter = filter;
+
+ return (0);
+}
+
int
portal_group_set_offload(struct portal_group *pg, const char *offload)
{
@@ -1626,6 +1686,12 @@
TAILQ_FOREACH(auth_portal, &ag->ag_initiator_portals, ap_next)
fprintf(stderr, "\t initiator-portal %s\n",
auth_portal->ap_portal);
+ TAILQ_FOREACH(auth_name, &ag->ag_host_names, an_next)
+ fprintf(stderr, "\t host-nqn %s\n",
+ auth_name->an_name);
+ TAILQ_FOREACH(auth_portal, &ag->ag_host_addresses, ap_next)
+ fprintf(stderr, "\t host-address %s\n",
+ auth_portal->ap_portal);
fprintf(stderr, "}\n");
}
TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) {
@@ -2591,6 +2657,9 @@
pg = portal_group_new(conf, TARGET_PROTOCOL_ISCSI, "default");
assert(pg != NULL);
+ pg = portal_group_new(conf, TARGET_PROTOCOL_NVME, "default");
+ assert(pg != NULL);
+
if (ucl)
error = uclparse_conf(conf, path);
else
@@ -2620,6 +2689,19 @@
portal_group_add_listen(pg, "[::]", PORTAL_PROTOCOL_ISCSI);
}
+ if (conf->conf_default_tg_defined == false) {
+ log_debugx("transport-group \"default\" not defined; "
+ "going with defaults");
+ pg = portal_group_find(conf, TARGET_PROTOCOL_NVME, "default");
+ assert(pg != NULL);
+ portal_group_add_listen(pg, "0.0.0.0",
+ PORTAL_PROTOCOL_NVME_DISCOVERY_TCP);
+ portal_group_add_listen(pg, "[::]",
+ PORTAL_PROTOCOL_NVME_DISCOVERY_TCP);
+ portal_group_add_listen(pg, "0.0.0.0", PORTAL_PROTOCOL_NVME_TCP);
+ portal_group_add_listen(pg, "[::]", PORTAL_PROTOCOL_NVME_TCP);
+ }
+
conf->conf_kernel_port_on = true;
error = conf_verify(conf);
diff --git a/usr.sbin/ctld/kernel.c b/usr.sbin/ctld/kernel.c
--- a/usr.sbin/ctld/kernel.c
+++ b/usr.sbin/ctld/kernel.c
@@ -117,8 +117,10 @@
char *port_name;
int pp;
int vp;
+ uint16_t portid;
int cfiscsi_state;
char *cfiscsi_target;
+ char *nqn;
uint16_t cfiscsi_portal_group_tag;
char *ctld_portal_group_name;
nvlist_t *attr_list;
@@ -358,6 +360,13 @@
} else if (strcmp(name, "ctld_portal_group_name") == 0) {
cur_port->ctld_portal_group_name = str;
str = NULL;
+ } else if (strcmp(name, "nqn") == 0) {
+ cur_port->nqn = str;
+ str = NULL;
+ } else if (strcmp(name, "portid") == 0) {
+ if (str == NULL)
+ log_errx(1, "%s: %s missing its argument", __func__, name);
+ cur_port->portid = strtoul(str, NULL, 0);
} else if (strcmp(name, "targ_port") == 0) {
devlist->cur_port = NULL;
} else if (strcmp(name, "ctlportlist") == 0) {
@@ -515,6 +524,8 @@
continue;
if (strcmp(port->port_frontend, "iscsi") == 0)
protocol = TARGET_PROTOCOL_ISCSI;
+ else if (strcmp(port->port_frontend, "nvmf") == 0)
+ protocol = TARGET_PROTOCOL_NVME;
else
/* XXX: Treat all unknown ports as iSCSI? */
protocol = TARGET_PROTOCOL_ISCSI;
@@ -537,6 +548,9 @@
case TARGET_PROTOCOL_ISCSI:
target_name = port->cfiscsi_target;
break;
+ case TARGET_PROTOCOL_NVME:
+ target_name = port->nqn;
+ break;
default:
__assert_unreachable();
break;
@@ -598,6 +612,9 @@
case TARGET_PROTOCOL_ISCSI:
pg->pg_tag = port->cfiscsi_portal_group_tag;
break;
+ case TARGET_PROTOCOL_NVME:
+ pg->pg_tag = port->portid;
+ break;
default:
__assert_unreachable();
break;
@@ -614,6 +631,7 @@
free(port->port_frontend);
free(port->port_name);
free(port->cfiscsi_target);
+ free(port->nqn);
free(port->ctld_portal_group_name);
nvlist_destroy(port->attr_list);
free(port);
@@ -1188,7 +1206,7 @@
kernel_capsicate(void)
{
cap_rights_t rights;
- const unsigned long cmds[] = { CTL_ISCSI };
+ const unsigned long cmds[] = { CTL_ISCSI, CTL_NVMF };
cap_rights_init(&rights, CAP_IOCTL);
if (caph_rights_limit(ctl_fd, &rights) < 0)
diff --git a/usr.sbin/ctld/nvme.c b/usr.sbin/ctld/nvme.c
new file mode 100644
--- /dev/null
+++ b/usr.sbin/ctld/nvme.c
@@ -0,0 +1,339 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Chelsio Communications, Inc.
+ * Written by: John Baldwin <jhb@FreeBSD.org>
+ */
+
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/time.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <libiscsiutil.h>
+#include <libnvmf.h>
+#include <libutil.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <cam/ctl/ctl.h>
+#include <cam/ctl/ctl_io.h>
+#include <cam/ctl/ctl_ioctl.h>
+
+#include "ctld.h"
+
+#define DEFAULT_MAXH2CDATA (256 * 1024)
+
+static uint16_t nvme_last_port_id = 0;
+
+static bool
+parse_bool(const nvlist_t *nvl, const char *key, bool def)
+{
+ const char *value;
+
+ if (!nvlist_exists_string(nvl, key))
+ return (def);
+
+ value = nvlist_get_string(nvl, key);
+ if (strcasecmp(value, "true") == 0 ||
+ strcasecmp(value, "1") == 0)
+ return (true);
+ if (strcasecmp(value, "false") == 0 ||
+ strcasecmp(value, "0") == 0)
+ return (false);
+
+ log_warnx("Invalid value \"%s\" for boolean option %s", value, key);
+ return (def);
+}
+
+static uint64_t
+parse_number(const nvlist_t *nvl, const char *key, uint64_t def, uint64_t minv,
+ uint64_t maxv)
+{
+ const char *value;
+ uint64_t uval;
+
+ if (!nvlist_exists_string(nvl, key))
+ return (def);
+
+ value = nvlist_get_string(nvl, key);
+ if (expand_number(value, &uval) == 0 && uval >= minv && uval <= maxv)
+ return (uval);
+
+ log_warnx("Invalid value \"%s\" for numeric option %s", value, key);
+ return (def);
+}
+
+/* Options shared between discovery and I/O associations. */
+static void
+nvme_aparams_from_options(struct portal_group *pg,
+ struct nvmf_association_params *params)
+{
+ uint64_t value;
+
+ params->tcp.header_digests = parse_bool(pg->pg_options, "HDGST", false);
+ params->tcp.data_digests = parse_bool(pg->pg_options, "DDGST", false);
+ value = parse_number(pg->pg_options, "MAXH2CDATA", DEFAULT_MAXH2CDATA,
+ 4096, UINT32_MAX);
+ if (value % 4 != 0) {
+ log_warnx("Invalid value \"%ju\" for option MAXH2CDATA",
+ (uintmax_t)value);
+ value = DEFAULT_MAXH2CDATA;
+ }
+ params->tcp.maxh2cdata = value;
+}
+
+static struct nvmf_association_params *
+nvme_init_discovery_aparams(struct portal_group *pg)
+{
+ struct nvmf_association_params *params;
+
+ params = calloc(1, sizeof(*params));
+ params->sq_flow_control = false;
+ params->dynamic_controller_model = true;
+ params->max_admin_qsize = NVME_MAX_ADMIN_ENTRIES;
+ params->tcp.pda = 0;
+ nvme_aparams_from_options(pg, params);
+
+ return (params);
+}
+
+static struct nvmf_association_params *
+nvme_init_io_aparams(struct portal_group *pg)
+{
+ struct nvmf_association_params *params;
+
+ params = calloc(1, sizeof(*params));
+ params->sq_flow_control = parse_bool(pg->pg_options, "SQFC", false);
+ params->dynamic_controller_model = true;
+ params->max_admin_qsize = parse_number(pg->pg_options,
+ "max_admin_qsize", NVME_MAX_ADMIN_ENTRIES, NVME_MIN_ADMIN_ENTRIES,
+ NVME_MAX_ADMIN_ENTRIES);
+ params->max_io_qsize = parse_number(pg->pg_options, "max_io_qsize",
+ NVME_MAX_IO_ENTRIES, NVME_MIN_IO_ENTRIES, NVME_MAX_IO_ENTRIES);
+ params->tcp.pda = 0;
+ nvme_aparams_from_options(pg, params);
+
+ return (params);
+}
+
+static void
+nvme_portal_group_init(struct portal_group *pg)
+{
+ pg->pg_tag = ++nvme_last_port_id;
+}
+
+static void
+nvme_portal_group_copy(struct portal_group *oldpg, struct portal_group *newpg)
+{
+ newpg->pg_tag = oldpg->pg_tag;
+}
+
+static void
+nvme_portal_init(struct portal *p)
+{
+ struct portal_group *pg = p->p_portal_group;
+ struct nvmf_association_params *aparams;
+ enum nvmf_trtype trtype;
+
+ switch (p->p_protocol) {
+ case PORTAL_PROTOCOL_NVME_TCP:
+ trtype = NVMF_TRTYPE_TCP;
+ aparams = nvme_init_io_aparams(pg);
+ break;
+ case PORTAL_PROTOCOL_NVME_DISCOVERY_TCP:
+ trtype = NVMF_TRTYPE_TCP;
+ aparams = nvme_init_discovery_aparams(pg);
+ break;
+ default:
+ __assert_unreachable();
+ }
+
+ p->p_nvme.aparams = aparams;
+ p->p_nvme.association = nvmf_allocate_association(trtype, true,
+ aparams);
+ if (p->p_nvme.association == NULL)
+ log_err(1, "Failed to create NVMe controller association");
+}
+
+static void
+nvme_portal_init_socket(struct portal *p __unused)
+{
+}
+
+static void
+nvme_portal_delete(struct portal *p)
+{
+ if (p->p_nvme.association != NULL)
+ nvmf_free_association(p->p_nvme.association);
+ free(p->p_nvme.aparams);
+}
+
+static void
+nvme_load_kernel_modules(struct portal_group *pg)
+{
+ struct portal *p;
+ static bool loaded;
+ bool tcp_transport;
+ int saved_errno;
+
+ if (loaded)
+ return;
+
+ saved_errno = errno;
+ if (modfind("nvmft") == -1 && kldload("nvmft") == -1)
+ log_warn("couldn't load nvmft");
+
+ tcp_transport = false;
+ TAILQ_FOREACH(p, &pg->pg_portals, p_next) {
+ switch (p->p_protocol) {
+ case PORTAL_PROTOCOL_NVME_TCP:
+ tcp_transport = true;
+ break;
+ }
+ }
+ if (tcp_transport) {
+ if (modfind("nvmf/tcp") == -1 && kldload("nvmf_tcp") == -1)
+ log_warn("couldn't load nvmf_tcp");
+ }
+
+ errno = saved_errno;
+ loaded = true;
+}
+
+static void
+nvme_kernel_port_add(struct port *port, struct ctl_req *req)
+{
+ struct target *targ = port->p_target;
+ struct portal_group *pg = port->p_portal_group;
+
+ nvme_load_kernel_modules(pg);
+
+ strlcpy(req->driver, "nvmf", sizeof(req->driver));
+
+ nvlist_add_string(req->args_nvl, "subnqn", targ->t_name);
+ nvlist_add_stringf(req->args_nvl, "portid", "%u", pg->pg_tag);
+ if (!nvlist_exists_string(req->args_nvl, "max_io_qsize"))
+ nvlist_add_stringf(req->args_nvl, "max_io_qsize", "%u",
+ NVME_MAX_IO_ENTRIES);
+}
+
+static void
+nvme_kernel_port_remove(struct port *port, struct ctl_req *req)
+{
+ struct target *targ = port->p_target;
+
+ strlcpy(req->driver, "nvmf", sizeof(req->driver));
+
+ nvlist_add_string(req->args_nvl, "subnqn", targ->t_name);
+}
+
+static char *
+nvme_validate_target_name(const char *name)
+{
+ char *t_name;
+ size_t i, len;
+
+ if (!nvmf_nqn_valid_strict(name)) {
+ log_warnx("controller name \"%s\" is invalid for NVMe", name);
+ return (NULL);
+ }
+
+ t_name = strdup(name);
+ if (t_name == NULL) {
+ log_warn("strdup");
+ return (NULL);
+ }
+
+ /*
+ * Normalize the name to lowercase to match iSCSI.
+ */
+ len = strlen(t_name);
+ for (i = 0; i < len; i++)
+ t_name[i] = tolower(t_name[i]);
+
+ return (t_name);
+}
+
+static void
+nvme_handle_io_socket(struct portal *portal, int fd)
+{
+ struct nvmf_fabric_connect_data data;
+ struct nvmf_qpair_params qparams;
+ struct ctl_nvmf req;
+ const struct nvmf_fabric_connect_cmd *cmd;
+ struct nvmf_capsule *nc;
+ struct nvmf_qpair *qp;
+ int error;
+
+ memset(&qparams, 0, sizeof(qparams));
+ qparams.tcp.fd = fd;
+
+ nc = NULL;
+ qp = nvmf_accept(portal->p_nvme.association, &qparams, &nc, &data);
+ if (qp == NULL) {
+ log_warnx("Failed to create NVMe I/O qpair: %s",
+ nvmf_association_error(portal->p_nvme.association));
+ goto error;
+ }
+ cmd = nvmf_capsule_sqe(nc);
+
+ memset(&req, 0, sizeof(req));
+ req.type = CTL_NVMF_HANDOFF;
+ error = nvmf_handoff_controller_qpair(qp, cmd, &data,
+ &req.data.handoff);
+ if (error != 0) {
+ log_warnc(error,
+ "Failed to prepare NVMe I/O qpair for handoff");
+ goto error;
+ }
+
+ if (ioctl(ctl_fd, CTL_NVMF, &req) != 0)
+ log_warn("ioctl(CTL_NVMF/CTL_NVMF_HANDOFF)");
+ if (req.status == CTL_NVMF_ERROR)
+ log_warnx("Failed to handoff NVMF connection: %s",
+ req.error_str);
+ else if (req.status != CTL_NVMF_OK)
+ log_warnx("Failed to handoff NVMF connection with status %d",
+ req.status);
+
+error:
+ if (nc != NULL)
+ nvmf_free_capsule(nc);
+ if (qp != NULL)
+ nvmf_free_qpair(qp);
+ close(fd);
+}
+
+static void
+nvme_handle_connection(struct portal *portal, int fd, const char *host __unused,
+ const struct sockaddr *client_sa)
+{
+ switch (portal->p_protocol) {
+ case PORTAL_PROTOCOL_NVME_TCP:
+ nvme_handle_io_socket(portal, fd);
+ break;
+ case PORTAL_PROTOCOL_NVME_DISCOVERY_TCP:
+ nvme_handle_discovery_socket(portal, fd, client_sa);
+ break;
+ default:
+ __assert_unreachable();
+ }
+}
+
+struct target_protocol_ops target_nvme = {
+ .portal_group_init = nvme_portal_group_init,
+ .portal_group_copy = nvme_portal_group_copy,
+ .portal_init = nvme_portal_init,
+ .portal_init_socket = nvme_portal_init_socket,
+ .portal_delete = nvme_portal_delete,
+ .kernel_port_add = nvme_kernel_port_add,
+ .kernel_port_remove = nvme_kernel_port_remove,
+ .validate_target_name = nvme_validate_target_name,
+ .handle_connection = nvme_handle_connection,
+};
diff --git a/usr.sbin/ctld/nvme_discovery.c b/usr.sbin/ctld/nvme_discovery.c
new file mode 100644
--- /dev/null
+++ b/usr.sbin/ctld/nvme_discovery.c
@@ -0,0 +1,532 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2023-2025 Chelsio Communications, Inc.
+ * Written by: John Baldwin <jhb@FreeBSD.org>
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <netdb.h>
+#include <libiscsiutil.h>
+#include <libnvmf.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <netinet/in.h>
+
+#include "ctld.h"
+
+struct io_controller_data {
+ struct nvme_discovery_log_entry entry;
+ bool wildcard;
+};
+
+struct controller {
+ struct nvmf_qpair *qp;
+
+ uint64_t cap;
+ uint32_t vs;
+ uint32_t cc;
+ uint32_t csts;
+
+ bool shutdown;
+
+ struct nvme_controller_data cdata;
+
+ struct portal *portal;
+ const struct sockaddr *client_sa;
+ char *hostnqn;
+ struct nvme_discovery_log *discovery_log;
+ size_t discovery_log_len;
+ int s;
+};
+
+static bool
+discovery_controller_filtered(struct controller *c,
+ const struct port *port)
+{
+ const struct portal_group *pg = c->portal->p_portal_group;
+ const struct target *targ = port->p_target;
+ const struct auth_group *ag;
+
+ ag = port->p_auth_group;
+ if (ag == NULL)
+ ag = targ->t_auth_group;
+
+ assert(pg->pg_discovery_auth_group != PG_FILTER_UNKNOWN);
+
+ if (pg->pg_discovery_filter >= PG_FILTER_PORTAL &&
+ auth_portal_check(ag, TARGET_PROTOCOL_NVME,
+ (const struct sockaddr_storage *)c->client_sa) != 0) {
+ log_debugx("host address does not match addresses "
+ "allowed for controller \"%s\"; skipping", targ->t_name);
+ return (true);
+ }
+
+
+ if (pg->pg_discovery_filter >= PG_FILTER_PORTAL_NAME &&
+ auth_name_check(ag, TARGET_PROTOCOL_NVME, c->hostnqn) != 0) {
+ log_debugx("HostNQN does not match NQNs "
+ "allowed for controller \"%s\"; skipping", targ->t_name);
+ return (true);
+ }
+
+ /* XXX: auth not yet implemented for NVMe */
+
+ return (false);
+}
+
+static bool
+portal_uses_wildcard_address(const struct portal *p)
+{
+ struct addrinfo *ai = p->p_ai;
+
+ switch (ai->ai_family) {
+ case AF_INET:
+ {
+ struct sockaddr_in *sin;
+
+ sin = (struct sockaddr_in *)ai->ai_addr;
+ return (sin->sin_addr.s_addr == htonl(INADDR_ANY));
+ }
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)ai->ai_addr;
+ return (memcmp(&sin6->sin6_addr, &in6addr_any,
+ sizeof(in6addr_any)) == 0);
+ }
+ default:
+ __assert_unreachable();
+ }
+}
+
+static bool
+init_discovery_log_entry(struct nvme_discovery_log_entry *entry,
+ struct target *target, struct portal *portal, const char *wildcard_host)
+{
+ const struct nvmf_association_params *aparams = portal->p_nvme.aparams;
+ struct portal_group *pg = portal->p_portal_group;
+ struct sockaddr_storage ss;
+ struct addrinfo *ai = portal->p_ai;
+ int error;
+ socklen_t len;
+
+ /*
+ * The default TCP port for I/O controllers is zero, so fetch
+ * the sockaddr of the socket to determine which port the
+ * kernel chose.
+ */
+ len = sizeof(ss);
+ if (getsockname(portal->p_socket, (struct sockaddr *)&ss, &len) == -1) {
+ log_warn("Failed getsockname building discovery log entry");
+ return (false);
+ }
+
+ memset(entry, 0, sizeof(*entry));
+ entry->trtype = NVMF_TRTYPE_TCP;
+ error = getnameinfo((struct sockaddr *)&ss, len, entry->traddr,
+ sizeof(entry->traddr), entry->trsvcid, sizeof(entry->trsvcid),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ if (error != 0) {
+ log_warnx("Failed getnameinfo building discovery log entry: %s",
+ gai_strerror(error));
+ return (false);
+ }
+
+ if (portal_uses_wildcard_address(portal))
+ strncpy(entry->traddr, wildcard_host, sizeof(entry->traddr));
+ switch (ai->ai_family) {
+ case AF_INET:
+ entry->adrfam = NVMF_ADRFAM_IPV4;
+ break;
+ case AF_INET6:
+ entry->adrfam = NVMF_ADRFAM_IPV6;
+ break;
+ default:
+ __assert_unreachable();
+ }
+ entry->subtype = NVMF_SUBTYPE_NVME;
+ if (!aparams->sq_flow_control)
+ entry->treq |= (1 << 2);
+ entry->portid = htole16(pg->pg_tag);
+ entry->cntlid = htole16(NVMF_CNTLID_DYNAMIC);
+ entry->aqsz = aparams->max_admin_qsize;
+ strncpy(entry->subnqn, target->t_name, sizeof(entry->subnqn));
+ return (true);
+}
+
+static void
+build_discovery_log_page(struct controller *c)
+{
+ struct portal_group *pg = c->portal->p_portal_group;
+ struct portal *portal;
+ struct port *port;
+ struct sockaddr_storage ss;
+ socklen_t len;
+ char wildcard_host[NI_MAXHOST];
+ u_int nentries;
+ int error;
+
+ if (c->discovery_log != NULL)
+ return;
+
+ len = sizeof(ss);
+ if (getsockname(c->s, (struct sockaddr *)&ss, &len) == -1) {
+ log_warn("build_discovery_log_page: getsockname");
+ return;
+ }
+
+ error = getnameinfo((struct sockaddr *)&ss, len, wildcard_host,
+ sizeof(wildcard_host), NULL, 0, NI_NUMERICHOST);
+ if (error != 0) {
+ log_warnx("build_discovery_log_page: getnameinfo: %s",
+ gai_strerror(error));
+ return;
+ }
+
+ nentries = 0;
+ TAILQ_FOREACH(port, &pg->pg_ports, p_pgs) {
+ if (discovery_controller_filtered(c, port))
+ continue;
+
+ TAILQ_FOREACH(portal, &pg->pg_portals, p_next) {
+ if (portal->p_protocol ==
+ PORTAL_PROTOCOL_NVME_DISCOVERY_TCP)
+ continue;
+
+ if (portal_uses_wildcard_address(portal) &&
+ portal->p_ai->ai_family != ss.ss_family)
+ continue;
+
+ nentries++;
+ }
+ }
+
+ c->discovery_log_len = sizeof(*c->discovery_log) +
+ nentries * sizeof(struct nvme_discovery_log_entry);
+ c->discovery_log = calloc(c->discovery_log_len, 1);
+ c->discovery_log->genctr = htole32(pg->pg_conf->conf_genctr);
+ c->discovery_log->recfmt = 0;
+ nentries = 0;
+ TAILQ_FOREACH(port, &pg->pg_ports, p_pgs) {
+ if (discovery_controller_filtered(c, port))
+ continue;
+
+ TAILQ_FOREACH(portal, &pg->pg_portals, p_next) {
+ if (portal->p_protocol ==
+ PORTAL_PROTOCOL_NVME_DISCOVERY_TCP)
+ continue;
+
+ if (portal_uses_wildcard_address(portal) &&
+ portal->p_ai->ai_family != ss.ss_family)
+ continue;
+
+ if (init_discovery_log_entry(
+ &c->discovery_log->entries[nentries],
+ port->p_target, portal, wildcard_host))
+ nentries++;
+ }
+ }
+ c->discovery_log->numrec = nentries;
+}
+
+static bool
+update_cc(struct controller *c, uint32_t new_cc)
+{
+ uint32_t changes;
+
+ if (c->shutdown)
+ return (false);
+ if (!nvmf_validate_cc(c->qp, c->cap, c->cc, new_cc))
+ return (false);
+
+ changes = c->cc ^ new_cc;
+ c->cc = new_cc;
+
+ /* Handle shutdown requests. */
+ if (NVMEV(NVME_CC_REG_SHN, changes) != 0 &&
+ NVMEV(NVME_CC_REG_SHN, new_cc) != 0) {
+ c->csts &= ~NVMEM(NVME_CSTS_REG_SHST);
+ c->csts |= NVMEF(NVME_CSTS_REG_SHST, NVME_SHST_COMPLETE);
+ c->shutdown = true;
+ }
+
+ if (NVMEV(NVME_CC_REG_EN, changes) != 0) {
+ if (NVMEV(NVME_CC_REG_EN, new_cc) == 0) {
+ /* Controller reset. */
+ c->csts = 0;
+ c->shutdown = true;
+ } else
+ c->csts |= NVMEF(NVME_CSTS_REG_RDY, 1);
+ }
+ return (true);
+}
+
+static void
+handle_property_get(const struct controller *c, const struct nvmf_capsule *nc,
+ const struct nvmf_fabric_prop_get_cmd *pget)
+{
+ struct nvmf_fabric_prop_get_rsp rsp;
+
+ nvmf_init_cqe(&rsp, nc, 0);
+
+ switch (le32toh(pget->ofst)) {
+ case NVMF_PROP_CAP:
+ if (pget->attrib.size != NVMF_PROP_SIZE_8)
+ goto error;
+ rsp.value.u64 = htole64(c->cap);
+ break;
+ case NVMF_PROP_VS:
+ if (pget->attrib.size != NVMF_PROP_SIZE_4)
+ goto error;
+ rsp.value.u32.low = htole32(c->vs);
+ break;
+ case NVMF_PROP_CC:
+ if (pget->attrib.size != NVMF_PROP_SIZE_4)
+ goto error;
+ rsp.value.u32.low = htole32(c->cc);
+ break;
+ case NVMF_PROP_CSTS:
+ if (pget->attrib.size != NVMF_PROP_SIZE_4)
+ goto error;
+ rsp.value.u32.low = htole32(c->csts);
+ break;
+ default:
+ goto error;
+ }
+
+ nvmf_send_response(nc, &rsp);
+ return;
+error:
+ nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD);
+}
+
+static void
+handle_property_set(struct controller *c, const struct nvmf_capsule *nc,
+ const struct nvmf_fabric_prop_set_cmd *pset)
+{
+ switch (le32toh(pset->ofst)) {
+ case NVMF_PROP_CC:
+ if (pset->attrib.size != NVMF_PROP_SIZE_4)
+ goto error;
+ if (!update_cc(c, le32toh(pset->value.u32.low)))
+ goto error;
+ break;
+ default:
+ goto error;
+ }
+
+ nvmf_send_success(nc);
+ return;
+error:
+ nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD);
+}
+
+static void
+handle_fabrics_command(struct controller *c, const struct nvmf_capsule *nc,
+ const struct nvmf_fabric_cmd *fc)
+{
+ switch (fc->fctype) {
+ case NVMF_FABRIC_COMMAND_PROPERTY_GET:
+ handle_property_get(c, nc,
+ (const struct nvmf_fabric_prop_get_cmd *)fc);
+ break;
+ case NVMF_FABRIC_COMMAND_PROPERTY_SET:
+ handle_property_set(c, nc,
+ (const struct nvmf_fabric_prop_set_cmd *)fc);
+ break;
+ case NVMF_FABRIC_COMMAND_CONNECT:
+ log_warnx("CONNECT command on connected queue");
+ nvmf_send_generic_error(nc, NVME_SC_COMMAND_SEQUENCE_ERROR);
+ break;
+ case NVMF_FABRIC_COMMAND_DISCONNECT:
+ log_warnx("DISCONNECT command on admin queue");
+ nvmf_send_error(nc, NVME_SCT_COMMAND_SPECIFIC,
+ NVMF_FABRIC_SC_INVALID_QUEUE_TYPE);
+ break;
+ default:
+ log_warnx("Unsupported fabrics command %#x", fc->fctype);
+ nvmf_send_generic_error(nc, NVME_SC_INVALID_OPCODE);
+ break;
+ }
+}
+
+static void
+handle_identify_command(const struct controller *c,
+ const struct nvmf_capsule *nc, const struct nvme_command *cmd)
+{
+ uint8_t cns;
+
+ cns = le32toh(cmd->cdw10) & 0xFF;
+ switch (cns) {
+ case 1:
+ break;
+ default:
+ log_warnx("Unsupported CNS %#x for IDENTIFY", cns);
+ goto error;
+ }
+
+ nvmf_send_controller_data(nc, &c->cdata, sizeof(c->cdata));
+ return;
+error:
+ nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD);
+}
+
+static void
+handle_get_log_page_command(struct controller *c,
+ const struct nvmf_capsule *nc, const struct nvme_command *cmd)
+{
+ uint64_t offset;
+ uint32_t length;
+
+ switch (nvmf_get_log_page_id(cmd)) {
+ case NVME_LOG_DISCOVERY:
+ break;
+ default:
+ log_warnx("Unsupported log page %u for discovery controller",
+ nvmf_get_log_page_id(cmd));
+ goto error;
+ }
+
+ build_discovery_log_page(c);
+
+ offset = nvmf_get_log_page_offset(cmd);
+ if (offset >= c->discovery_log_len)
+ goto error;
+
+ length = nvmf_get_log_page_length(cmd);
+ if (length > c->discovery_log_len - offset)
+ length = c->discovery_log_len - offset;
+
+ nvmf_send_controller_data(nc, (char *)c->discovery_log + offset,
+ length);
+ return;
+error:
+ nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD);
+}
+
+static void
+controller_handle_admin_commands(struct controller *c)
+{
+ struct nvmf_qpair *qp = c->qp;
+ const struct nvme_command *cmd;
+ struct nvmf_capsule *nc;
+ int error;
+
+ for (;;) {
+ error = nvmf_controller_receive_capsule(qp, &nc);
+ if (error != 0) {
+ if (error != ECONNRESET)
+ log_warnc(error,
+ "Failed to read command capsule");
+ break;
+ }
+
+ cmd = nvmf_capsule_sqe(nc);
+
+ /*
+ * Only permit Fabrics commands while a controller is
+ * disabled.
+ */
+ if (NVMEV(NVME_CC_REG_EN, c->cc) == 0 &&
+ cmd->opc != NVME_OPC_FABRICS_COMMANDS) {
+ log_warnx("Unsupported admin opcode %#x while disabled\n",
+ cmd->opc);
+ nvmf_send_generic_error(nc,
+ NVME_SC_COMMAND_SEQUENCE_ERROR);
+ nvmf_free_capsule(nc);
+ continue;
+ }
+
+ switch (cmd->opc) {
+ case NVME_OPC_FABRICS_COMMANDS:
+ handle_fabrics_command(c, nc,
+ (const struct nvmf_fabric_cmd *)cmd);
+ break;
+ case NVME_OPC_IDENTIFY:
+ handle_identify_command(c, nc, cmd);
+ break;
+ case NVME_OPC_GET_LOG_PAGE:
+ handle_get_log_page_command(c, nc, cmd);
+ break;
+ default:
+ log_warnx("Unsupported admin opcode %#x", cmd->opc);
+ nvmf_send_generic_error(nc, NVME_SC_INVALID_OPCODE);
+ break;
+ }
+ nvmf_free_capsule(nc);
+ }
+}
+
+static void
+nvme_discovery(struct portal *p, int s, const struct sockaddr *client_sa,
+ struct nvmf_qpair *qp, const struct nvmf_fabric_connect_data *data)
+{
+ struct controller c;
+
+ memset(&c, 0, sizeof(c));
+ c.portal = p;
+ c.client_sa = client_sa;
+ c.hostnqn = strndup(data->hostnqn, sizeof(data->hostnqn));
+ c.s = s;
+ c.qp = qp;
+ nvmf_init_discovery_controller_data(qp, &c.cdata);
+ c.cap = nvmf_controller_cap(qp);
+ c.vs = c.cdata.ver;
+
+ controller_handle_admin_commands(&c);
+
+ free(c.discovery_log);
+ free(c.hostnqn);
+}
+
+void
+nvme_handle_discovery_socket(struct portal *portal, int s,
+ const struct sockaddr *client_sa)
+{
+ struct nvmf_fabric_connect_data data;
+ struct nvmf_qpair_params qparams;
+ struct nvmf_capsule *nc;
+ struct nvmf_qpair *qp;
+ int error;
+
+ memset(&qparams, 0, sizeof(qparams));
+ qparams.tcp.fd = s;
+
+ nc = NULL;
+ qp = nvmf_accept(portal->p_nvme.association, &qparams, &nc, &data);
+ if (qp == NULL) {
+ log_warnx("Failed to create NVMe discovery qpair: %s",
+ nvmf_association_error(portal->p_nvme.association));
+ goto error;
+ }
+
+ if (strcmp(data.subnqn, NVMF_DISCOVERY_NQN) != 0) {
+ log_warnx("Discovery NVMe 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) {
+ log_warnc(error, "Failed to send NVMe CONNECT reponse");
+ goto error;
+ }
+ nvmf_free_capsule(nc);
+ nc = NULL;
+
+ nvme_discovery(portal, s, client_sa, qp, &data);
+error:
+ if (nc != NULL)
+ nvmf_free_capsule(nc);
+ if (qp != NULL)
+ nvmf_free_qpair(qp);
+ close(s);
+}
+
diff --git a/usr.sbin/ctld/parse.y b/usr.sbin/ctld/parse.y
--- a/usr.sbin/ctld/parse.y
+++ b/usr.sbin/ctld/parse.y
@@ -58,12 +58,14 @@
%}
%token ALIAS AUTH_GROUP AUTH_TYPE BACKEND BLOCKSIZE CHAP CHAP_MUTUAL
-%token CLOSING_BRACKET CTL_LUN DEBUG DEVICE_ID DEVICE_TYPE
-%token DISCOVERY_AUTH_GROUP DISCOVERY_FILTER DSCP FOREIGN
+%token CLOSING_BRACKET CONTROLLER CTL_LUN DEBUG DEVICE_ID DEVICE_TYPE
+%token DISCOVERY_AUTH_GROUP DISCOVERY_FILTER DISCOVERY_TCP DSCP FOREIGN
+%token HOST_ADDRESS HOST_NQN
%token INITIATOR_NAME INITIATOR_PORTAL ISNS_SERVER ISNS_PERIOD ISNS_TIMEOUT
-%token LISTEN LISTEN_ISER LUN MAXPROC OFFLOAD OPENING_BRACKET OPTION
+%token LISTEN LISTEN_ISER LUN MAXPROC NAMESPACE
+%token OFFLOAD OPENING_BRACKET OPTION
%token PATH PCP PIDFILE PORT PORTAL_GROUP REDIRECT SEMICOLON SERIAL
-%token SIZE STR TAG TARGET TIMEOUT
+%token SIZE STR TAG TARGET TCP TIMEOUT TRANSPORT_GROUP
%token AF11 AF12 AF13 AF21 AF22 AF23 AF31 AF32 AF33 AF41 AF42 AF43
%token BE EF CS0 CS1 CS2 CS3 CS4 CS5 CS6 CS7
@@ -102,9 +104,13 @@
|
portal_group
|
+ transport_group
+ |
lun
|
target
+ |
+ controller
;
debug: DEBUG STR
@@ -239,6 +245,10 @@
|
auth_group_chap_mutual
|
+ auth_group_host_address
+ |
+ auth_group_host_nqn
+ |
auth_group_initiator_name
|
auth_group_initiator_portal
@@ -281,6 +291,28 @@
}
;
+auth_group_host_address: HOST_ADDRESS STR
+ {
+ const struct auth_portal *ap;
+
+ ap = auth_portal_new(auth_group, TARGET_PROTOCOL_NVME, $2);
+ free($2);
+ if (ap == NULL)
+ return (1);
+ }
+ ;
+
+auth_group_host_nqn: HOST_NQN STR
+ {
+ const struct auth_name *an;
+
+ an = auth_name_new(auth_group, TARGET_PROTOCOL_NVME, $2);
+ free($2);
+ if (an == NULL)
+ return (1);
+ }
+ ;
+
auth_group_initiator_name: INITIATOR_NAME STR
{
const struct auth_name *an;
@@ -534,6 +566,114 @@
}
;
+transport_group: TRANSPORT_GROUP transport_group_name
+ OPENING_BRACKET transport_group_entries CLOSING_BRACKET
+ {
+ portal_group = NULL;
+ }
+ ;
+
+transport_group_name: STR
+ {
+ /*
+ * Make it possible to redefine default
+ * transport-group. but only once.
+ */
+ if (strcmp($1, "default") == 0 &&
+ conf->conf_default_tg_defined == false) {
+ portal_group = portal_group_find(conf,
+ TARGET_PROTOCOL_NVME, $1);
+ conf->conf_default_tg_defined = true;
+ } else {
+ portal_group = portal_group_new(conf,
+ TARGET_PROTOCOL_NVME, $1);
+ }
+ free($1);
+ if (portal_group == NULL)
+ return (1);
+ }
+ ;
+
+transport_group_entries:
+ |
+ transport_group_entries transport_group_entry
+ |
+ transport_group_entries transport_group_entry SEMICOLON
+ ;
+
+transport_group_entry:
+ transport_group_discovery_auth_group
+ |
+ transport_group_discovery_filter
+ |
+ transport_group_listen_discovery_tcp
+ |
+ transport_group_listen_tcp
+ |
+ portal_group_option
+ |
+ portal_group_tag
+ |
+ portal_group_dscp
+ |
+ portal_group_pcp
+ ;
+
+transport_group_discovery_auth_group: DISCOVERY_AUTH_GROUP STR
+ {
+ if (portal_group->pg_discovery_auth_group != NULL) {
+ log_warnx("discovery-auth-group for transport-group "
+ "\"%s\" specified more than once",
+ portal_group->pg_name);
+ return (1);
+ }
+ portal_group->pg_discovery_auth_group =
+ auth_group_find(conf, $2);
+ if (portal_group->pg_discovery_auth_group == NULL) {
+ log_warnx("unknown discovery-auth-group \"%s\" "
+ "for transport-group \"%s\"",
+ $2, portal_group->pg_name);
+ return (1);
+ }
+ free($2);
+ }
+ ;
+
+transport_group_discovery_filter: DISCOVERY_FILTER STR
+ {
+ int error;
+
+ error = transport_group_set_filter(portal_group, $2);
+ free($2);
+ if (error != 0)
+ return (1);
+ }
+ ;
+
+transport_group_listen_discovery_tcp: LISTEN DISCOVERY_TCP STR
+ {
+ int error;
+
+ error = portal_group_add_listen(portal_group, $3,
+ PORTAL_PROTOCOL_NVME_DISCOVERY_TCP);
+ free($3);
+ if (error != 0)
+ return (1);
+ }
+ ;
+
+transport_group_listen_tcp: LISTEN TCP STR
+ {
+ int error;
+
+ error = portal_group_add_listen(portal_group, $3,
+ PORTAL_PROTOCOL_NVME_TCP);
+ free($3);
+ if (error != 0)
+ return (1);
+ }
+ ;
+
lun: LUN lun_name
OPENING_BRACKET lun_entries CLOSING_BRACKET
{
@@ -918,6 +1058,229 @@
}
;
+controller: CONTROLLER controller_name
+ OPENING_BRACKET controller_entries CLOSING_BRACKET
+ {
+ target = NULL;
+ }
+ ;
+
+controller_name: STR
+ {
+ target = target_new(conf, $1, TARGET_PROTOCOL_NVME);
+ free($1);
+ if (target == NULL)
+ return (1);
+ }
+ ;
+
+controller_entries:
+ |
+ controller_entries controller_entry
+ |
+ controller_entries controller_entry SEMICOLON
+ ;
+
+controller_entry:
+ target_auth_group
+ |
+ target_auth_type
+ |
+ controller_host_address
+ |
+ controller_host_nqn
+ |
+ controller_transport_group
+ |
+ controller_namespace
+ |
+ controller_namespace_ref
+ ;
+
+controller_host_address: HOST_ADDRESS STR
+ {
+ const struct auth_portal *ap;
+
+ if (target->t_auth_group != NULL) {
+ if (target->t_auth_group->ag_name != NULL) {
+ log_warnx("cannot use both auth-group and "
+ "host-address for controller \"%s\"",
+ target->t_name);
+ free($2);
+ return (1);
+ }
+ } else {
+ target->t_auth_group = auth_group_new(conf, NULL);
+ if (target->t_auth_group == NULL) {
+ free($2);
+ return (1);
+ }
+ target->t_auth_group->ag_target = target;
+ }
+ ap = auth_portal_new(target->t_auth_group,
+ TARGET_PROTOCOL_NVME, $2);
+ free($2);
+ if (ap == NULL)
+ return (1);
+ }
+ ;
+
+controller_host_nqn: HOST_NQN STR
+ {
+ const struct auth_name *an;
+
+ if (target->t_auth_group != NULL) {
+ if (target->t_auth_group->ag_name != NULL) {
+ log_warnx("cannot use both auth-group and "
+ "host-nqn for controller \"%s\"",
+ target->t_name);
+ free($2);
+ return (1);
+ }
+ } else {
+ target->t_auth_group = auth_group_new(conf, NULL);
+ if (target->t_auth_group == NULL) {
+ free($2);
+ return (1);
+ }
+ target->t_auth_group->ag_target = target;
+ }
+ an = auth_name_new(target->t_auth_group, TARGET_PROTOCOL_NVME,
+ $2);
+ free($2);
+ if (an == NULL)
+ return (1);
+ }
+ ;
+
+controller_transport_group: TRANSPORT_GROUP STR STR
+ {
+ struct portal_group *tpg;
+ struct auth_group *tag;
+ struct port *tp;
+
+ tpg = portal_group_find(conf, TARGET_PROTOCOL_NVME, $2);
+ if (tpg == NULL) {
+ log_warnx("unknown transport-group \"%s\" for controller "
+ "\"%s\"", $2, target->t_name);
+ free($2);
+ free($3);
+ return (1);
+ }
+ tag = auth_group_find(conf, $3);
+ if (tag == NULL) {
+ log_warnx("unknown auth-group \"%s\" for controller "
+ "\"%s\"", $3, target->t_name);
+ free($2);
+ free($3);
+ return (1);
+ }
+ tp = port_new(conf, target, tpg);
+ if (tp == NULL) {
+ log_warnx("can't link transport-group \"%s\" to controller "
+ "\"%s\"", $2, target->t_name);
+ free($2);
+ return (1);
+ }
+ tp->p_auth_group = tag;
+ free($2);
+ free($3);
+ }
+ | TRANSPORT_GROUP STR
+ {
+ struct portal_group *tpg;
+ struct port *tp;
+
+ tpg = portal_group_find(conf, TARGET_PROTOCOL_NVME, $2);
+ if (tpg == NULL) {
+ log_warnx("unknown transport-group \"%s\" for controller "
+ "\"%s\"", $2, target->t_name);
+ free($2);
+ return (1);
+ }
+ tp = port_new(conf, target, tpg);
+ if (tp == NULL) {
+ log_warnx("can't link transport-group \"%s\" to controller "
+ "\"%s\"", $2, target->t_name);
+ free($2);
+ return (1);
+ }
+ free($2);
+ }
+ ;
+
+controller_namespace: NAMESPACE ns_number
+ OPENING_BRACKET lun_entries CLOSING_BRACKET
+ {
+ lun = NULL;
+ }
+ ;
+
+ns_number: STR
+ {
+ uint64_t tmp;
+ int ret;
+ char *name;
+
+ if (expand_number($1, &tmp) != 0) {
+ yyerror("invalid numeric value");
+ free($1);
+ return (1);
+ }
+ if (tmp == 0) {
+ yyerror("namespace ID cannot be 0");
+ free($1);
+ return (1);
+ }
+ if (tmp - 1 >= MAX_LUNS) {
+ yyerror("namespace ID is too big");
+ free($1);
+ return (1);
+ }
+
+ ret = asprintf(&name, "%s,nsid,%ju", target->t_name, tmp);
+ if (ret <= 0)
+ log_err(1, "asprintf");
+ lun = lun_new(conf, name);
+ if (lun == NULL)
+ return (1);
+
+ lun_set_scsiname(lun, name);
+ target->t_luns[tmp - 1] = lun;
+ }
+ ;
+
+controller_namespace_ref: NAMESPACE STR STR
+ {
+ uint64_t tmp;
+
+ if (expand_number($2, &tmp) != 0) {
+ yyerror("invalid numeric value");
+ free($2);
+ free($3);
+ return (1);
+ }
+ free($2);
+ if (tmp == 0) {
+ yyerror("namespace ID cannot be 0");
+ free($3);
+ return (1);
+ }
+ if (tmp - 1 >= MAX_LUNS) {
+ yyerror("namespace ID is too big");
+ free($3);
+ return (1);
+ }
+
+ lun = lun_find(conf, $3);
+ free($3);
+ if (lun == NULL)
+ return (1);
+
+ target->t_luns[tmp - 1] = lun;
+ }
+ ;
+
lun_entries:
|
lun_entries lun_entry
diff --git a/usr.sbin/ctld/token.l b/usr.sbin/ctld/token.l
--- a/usr.sbin/ctld/token.l
+++ b/usr.sbin/ctld/token.l
@@ -54,21 +54,26 @@
blocksize { return BLOCKSIZE; }
chap { return CHAP; }
chap-mutual { return CHAP_MUTUAL; }
+controller { return CONTROLLER; }
ctl-lun { return CTL_LUN; }
debug { return DEBUG; }
device-id { return DEVICE_ID; }
device-type { return DEVICE_TYPE; }
discovery-auth-group { return DISCOVERY_AUTH_GROUP; }
discovery-filter { return DISCOVERY_FILTER; }
+discovery-tcp { return DISCOVERY_TCP; }
dscp { return DSCP; }
pcp { return PCP; }
foreign { return FOREIGN; }
+host-address { return HOST_ADDRESS; }
+host-nqn { return HOST_NQN; }
initiator-name { return INITIATOR_NAME; }
initiator-portal { return INITIATOR_PORTAL; }
listen { return LISTEN; }
listen-iser { return LISTEN_ISER; }
lun { return LUN; }
maxproc { return MAXPROC; }
+namespace { return NAMESPACE; }
offload { return OFFLOAD; }
option { return OPTION; }
path { return PATH; }
@@ -83,7 +88,9 @@
size { return SIZE; }
tag { return TAG; }
target { return TARGET; }
+tcp { return TCP; }
timeout { return TIMEOUT; }
+transport-group { return TRANSPORT_GROUP; }
af11 { return AF11; }
af12 { return AF12; }
af13 { return AF13; }
diff --git a/usr.sbin/ctld/uclparse.c b/usr.sbin/ctld/uclparse.c
--- a/usr.sbin/ctld/uclparse.c
+++ b/usr.sbin/ctld/uclparse.c
@@ -51,6 +51,12 @@
static bool uclparse_lun(const char *, const ucl_object_t *);
static bool uclparse_auth_group(const char *, const ucl_object_t *);
static bool uclparse_portal_group(const char *, const ucl_object_t *);
+static bool uclparse_transport_group(const char *, const ucl_object_t *);
+static bool uclparse_controller(const char *, const ucl_object_t *);
+static bool uclparse_controller_transport_group(struct target *,
+ const ucl_object_t *);
+static bool uclparse_controller_namespace(struct target *,
+ const ucl_object_t *);
static bool uclparse_target(const char *, const ucl_object_t *);
static bool uclparse_target_portal_group(struct target *, const ucl_object_t *);
static bool uclparse_target_lun(struct target *, const ucl_object_t *);
@@ -185,6 +191,58 @@
return (true);
}
+static bool
+uclparse_controller_transport_group(struct target *target,
+ const ucl_object_t *obj)
+{
+ struct portal_group *tpg;
+ struct auth_group *tag = NULL;
+ struct port *tp;
+ const ucl_object_t *portal_group, *auth_group;
+
+ portal_group = ucl_object_find_key(obj, "name");
+ if (!portal_group || portal_group->type != UCL_STRING) {
+ log_warnx("transport-group section in controller \"%s\" is "
+ "missing \"name\" string key", target->t_name);
+ return (false);
+ }
+
+ auth_group = ucl_object_find_key(obj, "auth-group-name");
+ if (auth_group && auth_group->type != UCL_STRING) {
+ log_warnx("transport-group section in controller \"%s\" is "
+ "missing \"auth-group-name\" string key", target->t_name);
+ return (false);
+ }
+
+ tpg = portal_group_find(conf, TARGET_PROTOCOL_NVME,
+ ucl_object_tostring(portal_group));
+ if (tpg == NULL) {
+ log_warnx("unknown transport-group \"%s\" for controller "
+ "\"%s\"", ucl_object_tostring(portal_group), target->t_name);
+ return (false);
+ }
+
+ if (auth_group) {
+ tag = auth_group_find(conf, ucl_object_tostring(auth_group));
+ if (tag == NULL) {
+ log_warnx("unknown auth-group \"%s\" for controller "
+ "\"%s\"", ucl_object_tostring(auth_group),
+ target->t_name);
+ return (false);
+ }
+ }
+
+ tp = port_new(conf, target, tpg);
+ if (tp == NULL) {
+ log_warnx("can't link transport-group \"%s\" to controller "
+ "\"%s\"", ucl_object_tostring(portal_group), target->t_name);
+ return (false);
+ }
+ tp->p_auth_group = tag;
+
+ return (true);
+}
+
static bool
uclparse_target_lun(struct target *target, const ucl_object_t *obj)
{
@@ -243,6 +301,73 @@
return (true);
}
+static bool
+uclparse_controller_namespace(struct target *target, const ucl_object_t *obj)
+{
+ struct lun *lun;
+ uint64_t tmp;
+
+ if (obj->type == UCL_INT) {
+ char *name;
+
+ tmp = ucl_object_toint(obj);
+ if (tmp == 0) {
+ log_warnx("namespace ID cannot be 0");
+ return (false);
+ }
+ if (tmp - 1 >= MAX_LUNS) {
+ log_warnx("namespace ID %ju in controller \"%s\" is "
+ "too big", tmp, target->t_name);
+ return (false);
+ }
+
+ asprintf(&name, "%s,nsid,%ju", target->t_name, tmp);
+ lun = lun_new(conf, name);
+ if (lun == NULL)
+ return (false);
+
+ lun_set_scsiname(lun, name);
+ target->t_luns[tmp - 1] = lun;
+ return (true);
+ }
+
+ if (obj->type == UCL_OBJECT) {
+ const ucl_object_t *num = ucl_object_find_key(obj, "nsid");
+ const ucl_object_t *name = ucl_object_find_key(obj, "name");
+
+ if (num == NULL || num->type != UCL_INT) {
+ log_warnx("namespace section in controller \"%s\" is "
+ "missing \"nsid\" integer property",
+ target->t_name);
+ return (false);
+ }
+ tmp = ucl_object_toint(num);
+ if (tmp == 0) {
+ log_warnx("namespace ID cannot be 0");
+ return (false);
+ }
+ if (tmp - 1 >= MAX_LUNS) {
+ log_warnx("namespace ID %ju in controller \"%s\" is "
+ "too big", tmp, target->t_name);
+ return (false);
+ }
+
+ if (name == NULL || name->type != UCL_STRING) {
+ log_warnx("namespace section in controller \"%s\" is "
+ "missing \"name\" string property", target->t_name);
+ return (false);
+ }
+
+ lun = lun_find(conf, ucl_object_tostring(name));
+ if (lun == NULL)
+ return (false);
+
+ target->t_luns[tmp - 1] = lun;
+ }
+
+ return (true);
+}
+
static bool
uclparse_toplevel(const ucl_object_t *top)
{
@@ -354,6 +479,18 @@
}
}
+ if (!strcmp(key, "transport-group")) {
+ if (obj->type == UCL_OBJECT) {
+ iter = NULL;
+ while ((child = ucl_iterate_object(obj, &iter, true))) {
+ uclparse_transport_group(ucl_object_key(child), child);
+ }
+ } else {
+ log_warnx("\"transport-group\" section is not an object");
+ return (false);
+ }
+ }
+
if (!strcmp(key, "lun")) {
if (obj->type == UCL_OBJECT) {
iter = NULL;
@@ -372,6 +509,20 @@
while ((obj = ucl_iterate_object(top, &it, true))) {
const char *key = ucl_object_key(obj);
+ if (!strcmp(key, "controller")) {
+ if (obj->type == UCL_OBJECT) {
+ iter = NULL;
+ while ((child = ucl_iterate_object(obj, &iter,
+ true))) {
+ uclparse_controller(ucl_object_key(child),
+ child);
+ }
+ } else {
+ log_warnx("\"controller\" section is not an object");
+ return (false);
+ }
+ }
+
if (!strcmp(key, "target")) {
if (obj->type == UCL_OBJECT) {
iter = NULL;
@@ -453,6 +604,44 @@
}
}
+ if (!strcmp(key, "host-address")) {
+ if (obj->type != UCL_ARRAY) {
+ log_warnx("\"host-address\" property of "
+ "auth-group \"%s\" is not an array",
+ name);
+ return (false);
+ }
+
+ it2 = NULL;
+ while ((tmp = ucl_iterate_object(obj, &it2, true))) {
+ const char *value = ucl_object_tostring(tmp);
+
+ ap = auth_portal_new(auth_group,
+ TARGET_PROTOCOL_NVME, value);
+ if (ap == NULL)
+ return (false);
+ }
+ }
+
+ if (!strcmp(key, "host-nqn")) {
+ if (obj->type != UCL_ARRAY) {
+ log_warnx("\"host-nqn\" property of "
+ "auth-group \"%s\" is not an array",
+ name);
+ return (false);
+ }
+
+ it2 = NULL;
+ while ((tmp = ucl_iterate_object(obj, &it2, true))) {
+ const char *value = ucl_object_tostring(tmp);
+
+ an = auth_name_new(auth_group,
+ TARGET_PROTOCOL_NVME, value);
+ if (an == NULL)
+ return (false);
+ }
+ }
+
if (!strcmp(key, "initiator-name")) {
if (obj->type != UCL_ARRAY) {
log_warnx("\"initiator-name\" property of "
@@ -719,6 +908,296 @@
return (true);
}
+static int
+parse_transport_protocol(const char *name)
+{
+ if (strcmp(name, "tcp") == 0)
+ return (PORTAL_PROTOCOL_NVME_TCP);
+ else if (strcmp(name, "discovery-tcp") == 0)
+ return (PORTAL_PROTOCOL_NVME_DISCOVERY_TCP);
+ else
+ return (-1);
+}
+
+static bool
+uclparse_transport_listen_obj(struct portal_group *portal_group,
+ const ucl_object_t *top)
+{
+ ucl_object_iter_t it = NULL;
+ const ucl_object_t *obj = NULL;
+ const char *key;
+ int protocol;
+
+ while ((obj = ucl_iterate_object(top, &it, true))) {
+ key = ucl_object_key(obj);
+
+ protocol = parse_transport_protocol(key);
+ if (protocol < 0) {
+ log_warnx("invalid listen protocol \"%s\" for "
+ "transport-group \"%s\"", key,
+ portal_group->pg_name);
+ return (false);
+ }
+
+ if (portal_group_add_listen(portal_group,
+ ucl_object_tostring(obj), protocol) != 0)
+ return (false);
+ }
+ return (true);
+}
+
+static bool
+uclparse_transport_group(const char *name, const ucl_object_t *top)
+{
+ struct portal_group *portal_group;
+ ucl_object_iter_t it = NULL, it2 = NULL;
+ const ucl_object_t *obj = NULL, *tmp = NULL;
+ const char *key;
+
+ if (strcmp(name, "default") == 0 &&
+ conf->conf_default_tg_defined == false) {
+ portal_group = portal_group_find(conf, TARGET_PROTOCOL_NVME,
+ name);
+ conf->conf_default_tg_defined = true;
+ } else {
+ portal_group = portal_group_new(conf, TARGET_PROTOCOL_NVME,
+ name);
+ }
+
+ if (portal_group == NULL)
+ return (false);
+
+ while ((obj = ucl_iterate_object(top, &it, true))) {
+ key = ucl_object_key(obj);
+
+ if (!strcmp(key, "discovery-auth-group")) {
+ portal_group->pg_discovery_auth_group =
+ auth_group_find(conf, ucl_object_tostring(obj));
+ if (portal_group->pg_discovery_auth_group == NULL) {
+ log_warnx("unknown discovery-auth-group \"%s\" "
+ "for transport-group \"%s\"",
+ ucl_object_tostring(obj),
+ portal_group->pg_name);
+ return (false);
+ }
+ }
+
+ if (!strcmp(key, "discovery-filter")) {
+ if (obj->type != UCL_STRING) {
+ log_warnx("\"discovery-filter\" property of "
+ "portal-group \"%s\" is not a string",
+ portal_group->pg_name);
+ return (false);
+ }
+
+ if (transport_group_set_filter(portal_group,
+ ucl_object_tostring(obj)) != 0)
+ return (false);
+ }
+
+ if (!strcmp(key, "listen")) {
+ if (obj->type != UCL_OBJECT) {
+ log_warnx("missing protocol for \"listen\" "
+ "property of transport-group \"%s\"",
+ portal_group->pg_name);
+ return (false);
+ }
+ if (!uclparse_transport_listen_obj(portal_group, obj))
+ return (false);
+ }
+
+ if (!strcmp(key, "options")) {
+ if (obj->type != UCL_OBJECT) {
+ log_warnx("\"options\" property of transport group "
+ "\"%s\" is not an object", portal_group->pg_name);
+ return (false);
+ }
+
+ while ((tmp = ucl_iterate_object(obj, &it2,
+ true))) {
+ nvlist_add_string(portal_group->pg_options,
+ ucl_object_key(tmp),
+ ucl_object_tostring_forced(tmp));
+ }
+ }
+
+ if (!strcmp(key, "dscp")) {
+ if (!uclparse_dscp("transport", portal_group, obj))
+ return (false);
+ }
+
+ if (!strcmp(key, "pcp")) {
+ if (!uclparse_pcp("transport", portal_group, obj))
+ return (false);
+ }
+ }
+
+ return (true);
+}
+
+static bool
+uclparse_controller(const char *name, const ucl_object_t *top)
+{
+ struct target *target;
+ ucl_object_iter_t it = NULL, it2 = NULL;
+ const ucl_object_t *obj = NULL, *tmp = NULL;
+ const char *key;
+
+ target = target_new(conf, name, TARGET_PROTOCOL_NVME);
+ if (target == NULL)
+ return (false);
+
+ while ((obj = ucl_iterate_object(top, &it, true))) {
+ key = ucl_object_key(obj);
+
+ if (!strcmp(key, "auth-group")) {
+ const char *ag;
+
+ if (target->t_auth_group != NULL) {
+ if (target->t_auth_group->ag_name != NULL)
+ log_warnx("auth-group for controller "
+ "\"%s\" specified more than once",
+ target->t_name);
+ else
+ log_warnx("cannot use both auth-group "
+ "and explicit authorisations for "
+ "controller \"%s\"", target->t_name);
+ return (false);
+ }
+ ag = ucl_object_tostring(obj);
+ if (!ag) {
+ log_warnx("auth-group must be a string");
+ return (false);
+ }
+ target->t_auth_group = auth_group_find(conf, ag);
+ if (target->t_auth_group == NULL) {
+ log_warnx("unknown auth-group \"%s\" for "
+ "controller \"%s\"",
+ ucl_object_tostring(obj), target->t_name);
+ return (false);
+ }
+ }
+
+ if (!strcmp(key, "auth-type")) {
+ int error;
+
+ if (target->t_auth_group != NULL) {
+ if (target->t_auth_group->ag_name != NULL) {
+ log_warnx("cannot use both auth-group and "
+ "auth-type for controller \"%s\"",
+ target->t_name);
+ return (false);
+ }
+ } else {
+ target->t_auth_group = auth_group_new(conf, NULL);
+ if (target->t_auth_group == NULL)
+ return (false);
+
+ target->t_auth_group->ag_target = target;
+ }
+ error = auth_group_set_type(target->t_auth_group,
+ ucl_object_tostring(obj));
+ if (error != 0)
+ return (false);
+ }
+
+ if (!strcmp(key, "chap")) {
+ if (target->t_auth_group != NULL) {
+ if (target->t_auth_group->ag_name != NULL) {
+ log_warnx("cannot use both auth-group "
+ "and chap for controller \"%s\"",
+ target->t_name);
+ return (false);
+ }
+ } else {
+ target->t_auth_group = auth_group_new(conf, NULL);
+ if (target->t_auth_group == NULL) {
+ return (false);
+ }
+ target->t_auth_group->ag_target = target;
+ }
+ if (!uclparse_chap(target->t_auth_group, obj))
+ return (false);
+ }
+
+ if (!strcmp(key, "chap-mutual")) {
+ if (!uclparse_chap_mutual(target->t_auth_group, obj))
+ return (false);
+ }
+
+ if (!strcmp(key, "host-nqn")) {
+ const struct auth_name *an;
+
+ if (target->t_auth_group != NULL) {
+ if (target->t_auth_group->ag_name != NULL) {
+ log_warnx("cannot use both auth-group and "
+ "host-nqn for controller \"%s\"",
+ target->t_name);
+ return (false);
+ }
+ } else {
+ target->t_auth_group = auth_group_new(conf, NULL);
+ if (target->t_auth_group == NULL)
+ return (false);
+
+ target->t_auth_group->ag_target = target;
+ }
+ an = auth_name_new(target->t_auth_group,
+ TARGET_PROTOCOL_NVME, ucl_object_tostring(obj));
+ if (an == NULL)
+ return (false);
+ }
+
+ if (!strcmp(key, "host-address")) {
+ const struct auth_portal *ap;
+
+ if (target->t_auth_group != NULL) {
+ if (target->t_auth_group->ag_name != NULL) {
+ log_warnx("cannot use both auth-group and "
+ "host-address for controller \"%s\"",
+ target->t_name);
+ return (false);
+ }
+ } else {
+ target->t_auth_group = auth_group_new(conf, NULL);
+ if (target->t_auth_group == NULL)
+ return (false);
+
+ target->t_auth_group->ag_target = target;
+ }
+ ap = auth_portal_new(target->t_auth_group,
+ TARGET_PROTOCOL_NVME, ucl_object_tostring(obj));
+ if (ap == NULL)
+ return (false);
+ }
+
+ if (!strcmp(key, "transport-group")) {
+ if (obj->type == UCL_OBJECT) {
+ if (!uclparse_controller_transport_group(target, obj))
+ return (false);
+ }
+
+ if (obj->type == UCL_ARRAY) {
+ while ((tmp = ucl_iterate_object(obj, &it2,
+ true))) {
+ if (!uclparse_controller_transport_group(target,
+ tmp))
+ return (false);
+ }
+ }
+ }
+
+ if (!strcmp(key, "namespace")) {
+ while ((tmp = ucl_iterate_object(obj, &it2, true))) {
+ if (!uclparse_controller_namespace(target, tmp))
+ return (false);
+ }
+ }
+ }
+
+ return (true);
+}
+
static bool
uclparse_target(const char *name, const ucl_object_t *top)
{

File Metadata

Mime Type
text/plain
Expires
Tue, Feb 4, 6:36 PM (9 h, 58 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
16458956
Default Alt Text
D48773.id.diff (64 KB)

Event Timeline