Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F109338274
D48773.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
64 KB
Referenced Files
None
Subscribers
None
D48773.id.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D48773: ctld: Add support for NVMe over Fabrics
Attached
Detach File
Event Timeline
Log In to Comment