The branch main has been updated by jhb:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=66b5296f1b29083634e2875ff08c32e7b6b866a8

commit 66b5296f1b29083634e2875ff08c32e7b6b866a8
Author:     John Baldwin <j...@freebsd.org>
AuthorDate: 2025-08-06 19:57:50 +0000
Commit:     John Baldwin <j...@freebsd.org>
CommitDate: 2025-08-06 19:59:13 +0000

    ctld: Add support for NVMe over Fabrics
    
    While the overall structure is similar for NVMeoF controllers and
    iSCSI targets, there are sufficient differences that NVMe support uses
    an alternate configuration syntax.
    
    - In authentication groups, permitted NVMeoF hosts can be allowed by
      names (NQNs) via "host-nqn" values (similar to "initiator-name" for
      iSCSI).  Similarly, "host-address" accepts permitted host addresses
      similar to "initiator-portal" for iSCSI.
    
    - A new "transport-group" context enumerates transports that can be
      used by a group of NVMeoF controllers similar to the "portal-group"
      context for iSCSI.  In this section, the "listen" keyword accepts a
      transport as well as an address to permit other types of transports
      besides TCP in the future.  The "foreign", "offload", and "redirect"
      keywords are also not meaningful and thus not supported.
    
    - A new "controller" context describes an NVMeoF I/O controller
      similar to the "target" context for iSCSI.  One key difference here
      is that "lun" objects are replaced by "namespace" objects.  However,
      a "namespace" can reference a named global lun permitting LUNs to be
      shared between iSCSI targets and NVMeoF controllers.
    
    NB: Authentication via CHAP is not implemented for NVMeoF.
    
    Reviewed by:    imp
    Sponsored by:   Chelsio Communications
    Differential Revision:  https://reviews.freebsd.org/D48773
---
 usr.sbin/ctld/Makefile          |   6 +-
 usr.sbin/ctld/conf.cc           |  67 ++++++
 usr.sbin/ctld/conf.h            |  12 +
 usr.sbin/ctld/ctl.conf.5        | 242 ++++++++++++++++++-
 usr.sbin/ctld/ctld.cc           | 166 +++++++++++++
 usr.sbin/ctld/ctld.hh           |  43 +++-
 usr.sbin/ctld/discovery.cc      |   3 +
 usr.sbin/ctld/kernel.cc         |  54 ++++-
 usr.sbin/ctld/nvmf.cc           | 478 ++++++++++++++++++++++++++++++++++++
 usr.sbin/ctld/nvmf.hh           |  71 ++++++
 usr.sbin/ctld/nvmf_discovery.cc | 518 ++++++++++++++++++++++++++++++++++++++++
 usr.sbin/ctld/parse.y           | 232 +++++++++++++++++-
 usr.sbin/ctld/token.l           |   7 +
 usr.sbin/ctld/uclparse.cc       | 381 +++++++++++++++++++++++++++++
 14 files changed, 2263 insertions(+), 17 deletions(-)

diff --git a/usr.sbin/ctld/Makefile b/usr.sbin/ctld/Makefile
index ad3a2661794a..61efe8a05cfb 100644
--- a/usr.sbin/ctld/Makefile
+++ b/usr.sbin/ctld/Makefile
@@ -6,19 +6,21 @@ CFLAGS+=-I${SRCTOP}/contrib/libucl/include
 PACKAGE=       ctl
 PROG_CXX=      ctld
 SRCS=          ctld.cc conf.cc discovery.cc iscsi.cc isns.cc kernel.cc
-SRCS+=         login.cc parse.y token.l y.tab.h uclparse.cc
+SRCS+=         login.cc nvmf.cc nvmf_discovery.cc
+SRCS+=         parse.y token.l y.tab.h uclparse.cc
 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/libutil++
+CFLAGS+=       -I${SRCTOP}/lib/libnvmf
 #CFLAGS+=      -DICL_KERNEL_PROXY
 NO_WCAST_ALIGN=
 CXXWARNFLAGS.gcc= -Wno-shadow
 MAN=           ctld.8 ctl.conf.5
 
-LIBADD=                bsdxml iscsiutil md sbuf util ucl m nv util++
+LIBADD=                bsdxml iscsiutil nvmf md sbuf util ucl m nv util++
 
 YFLAGS+=       -v
 CLEANFILES=    y.tab.c y.tab.h y.output
diff --git a/usr.sbin/ctld/conf.cc b/usr.sbin/ctld/conf.cc
index d8f941e0bc52..ab76f8e2ed0b 100644
--- a/usr.sbin/ctld/conf.cc
+++ b/usr.sbin/ctld/conf.cc
@@ -122,6 +122,18 @@ auth_group_add_chap_mutual(const char *user, const char 
*secret,
        return (auth_group->add_chap_mutual(user, secret, user2, secret2));
 }
 
+bool
+auth_group_add_host_address(const char *portal)
+{
+       return (auth_group->add_host_address(portal));
+}
+
+bool
+auth_group_add_host_nqn(const char *name)
+{
+       return (auth_group->add_host_nqn(name));
+}
+
 bool
 auth_group_add_initiator_name(const char *name)
 {
@@ -233,6 +245,29 @@ portal_group_set_tag(uint16_t tag)
        portal_group->set_tag(tag);
 }
 
+bool
+transport_group_start(const char *name)
+{
+       if (strcmp(name, "default") == 0)
+               portal_group = conf->define_default_transport_group();
+       else
+               portal_group = conf->add_transport_group(name);
+       return (portal_group != NULL);
+}
+
+bool
+transport_group_add_listen_discovery_tcp(const char *listen)
+{
+       return portal_group->add_portal(listen,
+           portal_protocol::NVME_DISCOVERY_TCP);
+}
+
+bool
+transport_group_add_listen_tcp(const char *listen)
+{
+       return portal_group->add_portal(listen, portal_protocol::NVME_TCP);
+}
+
 bool
 lun_start(const char *name)
 {
@@ -387,6 +422,38 @@ target_start_lun(u_int id)
        return (lun != nullptr);
 }
 
+bool
+controller_start(const char *name)
+{
+       target = conf->add_controller(name);
+       return (target != nullptr);
+}
+
+bool
+controller_add_host_address(const char *addr)
+{
+       return (target->add_host_address(addr));
+}
+
+bool
+controller_add_host_nqn(const char *name)
+{
+       return (target->add_host_nqn(name));
+}
+
+bool
+controller_add_namespace(u_int id, const char *name)
+{
+       return (target->add_namespace(id, name));
+}
+
+bool
+controller_start_namespace(u_int id)
+{
+       lun = target->start_namespace(id);
+       return (lun != nullptr);
+}
+
 bool
 parse_conf(const char *path)
 {
diff --git a/usr.sbin/ctld/conf.h b/usr.sbin/ctld/conf.h
index b13fd80e9fe5..642c8f234d30 100644
--- a/usr.sbin/ctld/conf.h
+++ b/usr.sbin/ctld/conf.h
@@ -43,6 +43,8 @@ void  auth_group_finish(void);
 bool   auth_group_add_chap(const char *user, const char *secret);
 bool   auth_group_add_chap_mutual(const char *user, const char *secret,
            const char *user2, const char *secret2);
+bool   auth_group_add_host_address(const char *portal);
+bool   auth_group_add_host_nqn(const char *name);
 bool   auth_group_add_initiator_name(const char *name);
 bool   auth_group_add_initiator_portal(const char *portal);
 bool   auth_group_set_type(const char *type);
@@ -69,6 +71,10 @@ bool portal_group_set_pcp(u_int pcp);
 bool   portal_group_set_redirection(const char *addr);
 void   portal_group_set_tag(uint16_t tag);
 
+bool   transport_group_start(const char *name);
+bool   transport_group_add_listen_discovery_tcp(const char *listen);
+bool   transport_group_add_listen_tcp(const char *listen);
+
 bool   target_start(const char *name);
 void   target_finish(void);
 bool   target_add_chap(const char *user, const char *secret);
@@ -85,6 +91,12 @@ bool target_set_physical_port(const char *pport);
 bool   target_set_redirection(const char *addr);
 bool   target_start_lun(u_int id);
 
+bool   controller_start(const char *name);
+bool   controller_add_host_address(const char *addr);
+bool   controller_add_host_nqn(const char *name);
+bool   controller_add_namespace(u_int id, const char *name);
+bool   controller_start_namespace(u_int id);
+
 bool   lun_start(const char *name);
 void   lun_finish(void);
 bool   lun_add_option(const char *name, const char *value);
diff --git a/usr.sbin/ctld/ctl.conf.5 b/usr.sbin/ctld/ctl.conf.5
index e42dd8067006..12f4186a6844 100644
--- a/usr.sbin/ctld/ctl.conf.5
+++ b/usr.sbin/ctld/ctl.conf.5
@@ -26,12 +26,12 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd February 26, 2025
+.Dd August 6, 2025
 .Dt CTL.CONF 5
 .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 @@ file is:
 .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 @@ file is:
 .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 @@ Create a
 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 @@ the configuration may only contain either
 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,75 @@ to
 .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
+One of the following options:
+.Bl -column "max_admin_qsize" "Default" "Transports"
+.It Sy Name Ta Sy Default Ta Sy Transports Ta Sy Description
+.It MAXH2CDATA Ta 256KiB Ta TCP Ta
+Size in bytes of the maximum data payload size for data PDUs accepted from
+remote hosts.
+The value must be at least 4KiB and must be a multiple of 4.
+.It SQFC Ta false Ta any Ta
+Always enable SQ flow control.
+.It HDGST Ta false Ta TCP Ta
+Enable PDU header digests if requested by a remote host.
+.It DDGST Ta false Ta TCP Ta
+Enable PDU data digests if requested by a remote host.
+.It max_admin_qsize Ta 4096 Ta any Ta
+The maximum number of entries a remote host can request for an admin queue 
pair.
+.It max_io_qsize Ta 65536 Ta any Ta
+The maximum number of entries a remote host can request for an I/O queue pair.
+.El
+.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 +499,101 @@ configuration context, defining a LUN exported by the 
parent target.
 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 +614,7 @@ Global numeric identifier to use for a given LUN inside CTL.
 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 +629,11 @@ section of
 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 +702,16 @@ target naa.50015178f369f092 {
        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
@@ -585,6 +799,22 @@ target {
                }
        }
 }
+
+controller {
+       "nqn.2012-06.com.example:controller1" {
+               auth-group = no-authentication
+               namespace = {
+                       1 = example_1,
+                       2 {
+                               backend = ramdisk
+                               size = 1G
+                               options {
+                                       capacity = 1G
+                               }
+                       }
+               }
+       }
+}
 .Ed
 .Sh SEE ALSO
 .Xr ctl 4 ,
diff --git a/usr.sbin/ctld/ctld.cc b/usr.sbin/ctld/ctld.cc
index 451245b8d5fa..10c12f25068e 100644
--- a/usr.sbin/ctld/ctld.cc
+++ b/usr.sbin/ctld/ctld.cc
@@ -41,6 +41,7 @@
 #include <assert.h>
 #include <ctype.h>
 #include <errno.h>
+#include <libnvmf.h>
 #include <netdb.h>
 #include <signal.h>
 #include <stdbool.h>
@@ -67,6 +68,8 @@ static volatile bool sigalrm_received = false;
 static int kqfd;
 static int nchildren = 0;
 
+uint32_t conf::global_genctr;
+
 static void
 usage(void)
 {
@@ -76,6 +79,11 @@ usage(void)
        exit(1);
 }
 
+conf::conf()
+{
+       conf_genctr = global_genctr++;
+}
+
 void
 conf::set_debug(int debug)
 {
@@ -277,6 +285,23 @@ auth_group::add_chap_mutual(const char *user, const char 
*secret,
        return (true);
 }
 
+bool
+auth_group::add_host_nqn(std::string_view nqn)
+{
+       /* Silently ignore duplicates. */
+       ag_host_names.emplace(nqn);
+       return (true);
+}
+
+bool
+auth_group::host_permitted(std::string_view nqn) const
+{
+       if (ag_host_names.empty())
+               return (true);
+
+       return (ag_host_names.count(std::string(nqn)) != 0);
+}
+
 bool
 auth_group::add_initiator_name(std::string_view name)
 {
@@ -360,6 +385,20 @@ auth_portal::parse(const char *portal)
        return true;
 }
 
+bool
+auth_group::add_host_address(const char *address)
+{
+       auth_portal ap;
+       if (!ap.parse(address)) {
+               log_warnx("invalid controller address \"%s\" for %s", address,
+                   label());
+               return (false);
+       }
+
+       ag_host_addresses.emplace_back(ap);
+       return (true);
+}
+
 bool
 auth_group::add_initiator_portal(const char *portal)
 {
@@ -406,6 +445,18 @@ auth_portal::matches(const struct sockaddr *sa) const
        return (true);
 }
 
+bool
+auth_group::host_permitted(const struct sockaddr *sa) const
+{
+       if (ag_host_addresses.empty())
+               return (true);
+
+       for (const auth_portal &ap : ag_host_addresses)
+               if (ap.matches(sa))
+                       return (true);
+       return (false);
+}
+
 bool
 auth_group::initiator_permitted(const struct sockaddr *sa) const
 {
@@ -501,6 +552,45 @@ conf::find_portal_group(std::string_view name)
        return (it->second.get());
 }
 
+struct portal_group *
+conf::add_transport_group(const char *name)
+{
+       auto pair = conf_transport_groups.try_emplace(name,
+           nvmf_make_transport_group(this, name));
+       if (!pair.second) {
+               log_warnx("duplicated transport-group \"%s\"", name);
+               return (nullptr);
+       }
+
+       return (pair.first->second.get());
+}
+
+/*
+ * Make it possible to redefine the default transport-group, but only
+ * once.
+ */
+struct portal_group *
+conf::define_default_transport_group()
+{
+       if (conf_default_tg_defined) {
+               log_warnx("duplicated transport-group \"default\"");
+               return (nullptr);
+       }
+
+       conf_default_tg_defined = true;
+       return (find_transport_group("default"));
+}
+
+struct portal_group *
+conf::find_transport_group(std::string_view name)
+{
+       auto it = conf_transport_groups.find(std::string(name));
+       if (it == conf_transport_groups.end())
+               return (nullptr);
+
+       return (it->second.get());
+}
+
 bool
 portal_group::is_dummy() const
 {
@@ -1113,6 +1203,40 @@ portal_group::find_port(std::string_view target) const
        return (it->second);
 }
 
+struct target *
+conf::add_controller(const char *name)
+{
+       if (!nvmf_nqn_valid_strict(name)) {
+               log_warnx("controller name \"%s\" is invalid for NVMe", name);
+               return nullptr;
+       }
+
+       /*
+        * Normalize the name to lowercase to match iSCSI.
+        */
+       std::string t_name(name);
+       for (char &c : t_name)
+               c = tolower(c);
+
+       auto const &pair = conf_controllers.try_emplace(t_name,
+           nvmf_make_controller(this, t_name));
+       if (!pair.second) {
+               log_warnx("duplicated controller \"%s\"", name);
+               return nullptr;
+       }
+
+       return pair.first->second.get();
+}
+
+struct target *
+conf::find_controller(std::string_view name)
+{
+       auto it = conf_controllers.find(std::string(name));
+       if (it == conf_controllers.end())
+               return nullptr;
+       return it->second.get();
+}
+
 target::target(struct conf *conf, const char *keyword, std::string_view name) :
        t_conf(conf), t_name(name)
 {
@@ -1367,6 +1491,8 @@ conf::delete_target_luns(struct lun *lun)
 {
        for (const auto &kv : conf_targets)
                kv.second->remove_lun(lun);
+       for (const auto &kv : conf_controllers)
+               kv.second->remove_lun(lun);
 }
 
 struct lun *
@@ -1667,9 +1793,15 @@ conf::verify()
        for (auto &kv : conf_targets) {
                kv.second->verify();
        }
+       for (auto &kv : conf_controllers) {
+               kv.second->verify();
+       }
        for (auto &kv : conf_portal_groups) {
                kv.second->verify(this);
        }
+       for (auto &kv : conf_transport_groups) {
+               kv.second->verify(this);
+       }
        for (const auto &kv : conf_auth_groups) {
                const std::string &ag_name = kv.first;
                if (ag_name == "default" ||
@@ -1813,6 +1945,12 @@ conf::reuse_portal_group_socket(struct portal &newp)
                if (pg.reuse_socket(newp))
                        return (true);
        }
+       for (auto &kv : conf_transport_groups) {
+               struct portal_group &pg = *kv.second;
+
+               if (pg.reuse_socket(newp))
+                       return (true);
+       }
        return (false);
 }
 
@@ -1864,6 +2002,17 @@ conf::apply(struct conf *oldconf)
                else
                        newpg.allocate_tag();
        }
+       for (auto &kv : conf_transport_groups) {
+               struct portal_group &newpg = *kv.second;
+
+               if (newpg.tag() != 0)
+                       continue;
+               auto it = oldconf->conf_transport_groups.find(kv.first);
+               if (it != oldconf->conf_transport_groups.end())
+                       newpg.set_tag(it->second->tag());
+               else
+                       newpg.allocate_tag();
+       }
 
        /* Deregister on removed iSNS servers. */
        for (auto &kv : oldconf->conf_isns) {
@@ -2027,6 +2176,9 @@ conf::apply(struct conf *oldconf)
        for (auto &kv : conf_portal_groups) {
                cumulated_error += kv.second->open_sockets(*oldconf);
        }
+       for (auto &kv : conf_transport_groups) {
+               cumulated_error += kv.second->open_sockets(*oldconf);
+       }
 
        /*
         * Go through the no longer used sockets, closing them.
@@ -2034,6 +2186,9 @@ conf::apply(struct conf *oldconf)
        for (auto &kv : oldconf->conf_portal_groups) {
                kv.second->close_sockets();
        }
+       for (auto &kv : oldconf->conf_transport_groups) {
+               kv.second->close_sockets();
+       }
 
        /* (Re-)Register on remaining/new iSNS servers. */
        for (auto &kv : conf_isns) {
@@ -2397,6 +2552,9 @@ conf_new_from_file(const char *path, bool ucl)
        pg = conf->add_portal_group("default");
        assert(pg != NULL);
 
+       pg = conf->add_transport_group("default");
+       assert(pg != NULL);
+
        conf_start(conf.get());
        if (ucl)
                valid = uclparse_conf(path);
@@ -2427,6 +2585,14 @@ conf_new_from_file(const char *path, bool ucl)
                pg->add_default_portals();
        }
 
+       if (!conf->default_portal_group_defined()) {
+               log_debugx("transport-group \"default\" not defined; "
+                   "going with defaults");
+               pg = conf->find_transport_group("default");
+               assert(pg != NULL);
+               pg->add_default_portals();
+       }
+
        if (!conf->verify()) {
                conf.reset();
                return {};
diff --git a/usr.sbin/ctld/ctld.hh b/usr.sbin/ctld/ctld.hh
index 6ecee3b73c4f..a5aab65e339b 100644
--- a/usr.sbin/ctld/ctld.hh
+++ b/usr.sbin/ctld/ctld.hh
@@ -110,6 +110,12 @@ struct auth_group {
            const char *user2, const char *secret2);
        const struct auth *find_auth(std::string_view user) const;
 
+       bool add_host_nqn(std::string_view nqn);
+       bool host_permitted(std::string_view nqn) const;
+
+       bool add_host_address(const char *address);
+       bool host_permitted(const struct sockaddr *sa) const;
+
        bool add_initiator_name(std::string_view initiator_name);
        bool initiator_permitted(std::string_view initiator_name) const;
 
@@ -123,6 +129,8 @@ private:
        std::string                     ag_label;
        auth_type                       ag_type = auth_type::UNKNOWN;
        std::unordered_map<std::string, auth> ag_auths;
+       std::unordered_set<std::string> ag_host_names;
+       std::list<auth_portal>          ag_host_addresses;
        std::unordered_set<std::string> ag_initiator_names;
        std::list<auth_portal>          ag_initiator_portals;
 };
@@ -131,7 +139,9 @@ using auth_group_sp = std::shared_ptr<auth_group>;
 
 enum class portal_protocol {
        ISCSI,
-       ISER
+       ISER,
+       NVME_TCP,
+       NVME_DISCOVERY_TCP,
 };
 
 struct portal {
@@ -147,7 +157,7 @@ struct portal {
        virtual void handle_connection(freebsd::fd_up fd, const char *host,
            const struct sockaddr *client_sa) = 0;
 
-       portal_group *portal_group() { return p_portal_group; }
+       portal_group *portal_group() const { return p_portal_group; }
        const char *listen() const { return p_listen.c_str(); }
        const addrinfo *ai() const { return p_ai.get(); }
        portal_protocol protocol() const { return p_protocol; }
@@ -386,9 +396,12 @@ struct target {
        bool add_chap(const char *user, const char *secret);
        bool add_chap_mutual(const char *user, const char *secret,
            const char *user2, const char *secret2);
+       virtual bool add_host_address(const char *) { return false; }
+       virtual bool add_host_nqn(std::string_view) { return false; }
        virtual bool add_initiator_name(std::string_view) { return false; }
        virtual bool add_initiator_portal(const char *) { return false; }
        virtual bool add_lun(u_int, const char *) { return false; }
+       virtual bool add_namespace(u_int, const char *) { return false; }
        virtual bool add_portal_group(const char *pg_name,
            const char *ag_name) = 0;
        bool set_alias(std::string_view alias);
@@ -397,6 +410,7 @@ struct target {
        bool set_physical_port(std::string_view pport);
        bool set_redirection(const char *addr);
        virtual struct lun *start_lun(u_int) { return nullptr; }
+       virtual struct lun *start_namespace(u_int) { return nullptr; }
 
        void add_port(struct port *port);
        void remove_lun(struct lun *lun);
@@ -440,13 +454,18 @@ private:
 };
 
 struct conf {
+       conf();
+
        int maxproc() const { return conf_maxproc; }
        int timeout() const { return conf_timeout; }
+       uint32_t genctr() const { return conf_genctr; }
 
        bool default_auth_group_defined() const
        { return conf_default_ag_defined; }
        bool default_portal_group_defined() const
        { return conf_default_pg_defined; }
+       bool default_transport_group_defined() const
+       { return conf_default_tg_defined; }
 
        struct auth_group *add_auth_group(const char *ag_name);
        struct auth_group *define_default_auth_group();
@@ -456,6 +475,10 @@ struct conf {
        struct portal_group *define_default_portal_group();
        struct portal_group *find_portal_group(std::string_view name);
 
+       struct portal_group *add_transport_group(const char *name);
+       struct portal_group *define_default_transport_group();
+       struct portal_group *find_transport_group(std::string_view name);
+
        bool add_port(struct target *target, struct portal_group *pg,
            auth_group_sp ag);
        bool add_port(struct target *target, struct portal_group *pg,
@@ -465,6 +488,9 @@ struct conf {
            int vp);
        bool add_pports(struct kports &kports);
 
+       struct target *add_controller(const char *name);
+       struct target *find_controller(std::string_view name);
+
        struct target *add_target(const char *name);
        struct target *find_target(std::string_view name);
 
@@ -501,10 +527,12 @@ private:
 
        std::string                     conf_pidfile_path;
        std::unordered_map<std::string, std::unique_ptr<lun>> conf_luns;
-       std::unordered_map<std::string, std::unique_ptr<target>> conf_targets;
+       std::unordered_map<std::string, target_up> conf_targets;
+       std::unordered_map<std::string, target_up> conf_controllers;
        std::unordered_map<std::string, auth_group_sp> conf_auth_groups;
        std::unordered_map<std::string, std::unique_ptr<port>> conf_ports;
        std::unordered_map<std::string, portal_group_up> conf_portal_groups;
+       std::unordered_map<std::string, portal_group_up> conf_transport_groups;
        std::unordered_map<std::string, isns> conf_isns;
        struct target                   *conf_first_target = nullptr;
        int                             conf_isns_period = 900;
@@ -512,12 +540,16 @@ private:
        int                             conf_debug = 0;
        int                             conf_timeout = 60;
        int                             conf_maxproc = 30;
+       uint32_t                        conf_genctr = 0;
 
        freebsd::pidfile                conf_pidfile;
 
        bool                            conf_default_pg_defined = false;
+       bool                            conf_default_tg_defined = false;
        bool                            conf_default_ag_defined = false;
 
+       static uint32_t                 global_genctr;
+
 #ifdef ICL_KERNEL_PROXY
 public:
        int add_proxy_portal(portal *);
@@ -593,6 +625,11 @@ portal_group_up            iscsi_make_portal_group(struct 
conf *conf,
 target_up              iscsi_make_target(struct conf *conf,
                            std::string_view name);
 
+portal_group_up                nvmf_make_transport_group(struct conf *conf,
+                           std::string_view name);
+target_up              nvmf_make_controller(struct conf *conf,
+                           std::string_view name);
+
 void                   start_timer(int timeout, bool fatal = false);
 void                   stop_timer();
*** 1962 LINES SKIPPED ***

Reply via email to