aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/ctld
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/ctld')
-rw-r--r--usr.sbin/ctld/Makefile12
-rw-r--r--usr.sbin/ctld/conf.cc72
-rw-r--r--usr.sbin/ctld/conf.h12
-rw-r--r--usr.sbin/ctld/ctl.conf.5242
-rw-r--r--usr.sbin/ctld/ctld.cc531
-rw-r--r--usr.sbin/ctld/ctld.hh174
-rw-r--r--usr.sbin/ctld/discovery.cc8
-rw-r--r--usr.sbin/ctld/iscsi.cc508
-rw-r--r--usr.sbin/ctld/iscsi.hh79
-rw-r--r--usr.sbin/ctld/kernel.cc251
-rw-r--r--usr.sbin/ctld/login.cc17
-rw-r--r--usr.sbin/ctld/nvmf.cc478
-rw-r--r--usr.sbin/ctld/nvmf.hh71
-rw-r--r--usr.sbin/ctld/nvmf_discovery.cc518
-rw-r--r--usr.sbin/ctld/parse.y232
-rw-r--r--usr.sbin/ctld/token.l7
-rw-r--r--usr.sbin/ctld/uclparse.cc383
17 files changed, 3077 insertions, 518 deletions
diff --git a/usr.sbin/ctld/Makefile b/usr.sbin/ctld/Makefile
index 26cc03c036cb..61efe8a05cfb 100644
--- a/usr.sbin/ctld/Makefile
+++ b/usr.sbin/ctld/Makefile
@@ -5,30 +5,28 @@ CFLAGS+=-I${SRCTOP}/contrib/libucl/include
PACKAGE= ctl
PROG_CXX= ctld
-SRCS= ctld.cc conf.cc discovery.cc isns.cc kernel.cc
-SRCS+= login.cc parse.y token.l y.tab.h uclparse.cc
+SRCS= ctld.cc conf.cc discovery.cc iscsi.cc isns.cc kernel.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
NO_WMISSING_VARIABLE_DECLARATIONS=
-.if ${MK_ISCSI} != "no"
-CFLAGS+= -DWANT_ISCSI
-.endif
-
.include <bsd.prog.mk>
CXXWARNFLAGS.uclparse.cc= -Wno-shadow -Wno-cast-qual
diff --git a/usr.sbin/ctld/conf.cc b/usr.sbin/ctld/conf.cc
index 2eae12c31d0c..56a149a58a25 100644
--- a/usr.sbin/ctld/conf.cc
+++ b/usr.sbin/ctld/conf.cc
@@ -123,6 +123,18 @@ auth_group_add_chap_mutual(const char *user, const char *secret,
}
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)
{
return (auth_group->add_initiator_name(name));
@@ -175,7 +187,8 @@ portal_group_finish(void)
bool
portal_group_add_listen(const char *listen, bool iser)
{
- return (portal_group->add_portal(listen, iser));
+ return (portal_group->add_portal(listen, iser ? portal_protocol::ISER :
+ portal_protocol::ISCSI));
}
bool
@@ -233,6 +246,29 @@ portal_group_set_tag(uint16_t 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)
{
lun = conf->add_lun(name);
@@ -387,6 +423,38 @@ target_start_lun(u_int id)
}
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)
{
freebsd::FILE_up fp(fopen(path, "r"));
@@ -398,7 +466,7 @@ parse_conf(const char *path)
bool parsed;
try {
parsed = yyparse_conf(fp.get());
- } catch (std::bad_alloc) {
+ } catch (std::bad_alloc &) {
log_warnx("failed to allocate memory parsing %s", path);
return (false);
} catch (...) {
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 23d01364a6a4..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>
@@ -58,13 +59,6 @@
#include "ctld.hh"
#include "isns.hh"
-static bool timed_out(void);
-#ifdef ICL_KERNEL_PROXY
-static void pdu_receive_proxy(struct pdu *pdu);
-static void pdu_send_proxy(struct pdu *pdu);
-#endif /* ICL_KERNEL_PROXY */
-static void pdu_fail(const struct connection *conn, const char *reason);
-
bool proxy_mode = false;
static volatile bool sighup_received = false;
@@ -73,19 +67,8 @@ static volatile bool sigalrm_received = false;
static int kqfd;
static int nchildren = 0;
-static uint16_t last_portal_group_tag = 0xff;
-static struct connection_ops conn_ops = {
- .timed_out = timed_out,
-#ifdef ICL_KERNEL_PROXY
- .pdu_receive_proxy = pdu_receive_proxy,
- .pdu_send_proxy = pdu_send_proxy,
-#else
- .pdu_receive_proxy = nullptr,
- .pdu_send_proxy = nullptr,
-#endif
- .fail = pdu_fail,
-};
+uint32_t conf::global_genctr;
static void
usage(void)
@@ -96,6 +79,11 @@ usage(void)
exit(1);
}
+conf::conf()
+{
+ conf_genctr = global_genctr++;
+}
+
void
conf::set_debug(int debug)
{
@@ -298,20 +286,37 @@ auth_group::add_chap_mutual(const char *user, const char *secret,
}
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)
{
/* Silently ignore duplicates. */
- ag_names.emplace(name);
+ ag_initiator_names.emplace(name);
return (true);
}
bool
auth_group::initiator_permitted(std::string_view initiator_name) const
{
- if (ag_names.empty())
+ if (ag_initiator_names.empty())
return (true);
- return (ag_names.count(std::string(initiator_name)) != 0);
+ return (ag_initiator_names.count(std::string(initiator_name)) != 0);
}
bool
@@ -381,6 +386,20 @@ auth_portal::parse(const char *portal)
}
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)
{
auth_portal ap;
@@ -390,7 +409,7 @@ auth_group::add_initiator_portal(const char *portal)
return (false);
}
- ag_portals.emplace_back(ap);
+ ag_initiator_portals.emplace_back(ap);
return (true);
}
@@ -427,12 +446,24 @@ auth_portal::matches(const struct sockaddr *sa) const
}
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
{
- if (ag_portals.empty())
+ if (ag_initiator_portals.empty())
return (true);
- for (const auth_portal &ap : ag_portals)
+ for (const auth_portal &ap : ag_initiator_portals)
if (ap.matches(sa))
return (true);
return (false);
@@ -477,8 +508,8 @@ conf::find_auth_group(std::string_view name)
return (it->second);
}
-portal_group::portal_group(struct conf *conf, std::string_view name)
- : pg_conf(conf), pg_options(nvlist_create(0)), pg_name(name)
+portal_group::portal_group(struct conf *conf, std::string_view name) :
+ pg_conf(conf), pg_options(nvlist_create(0)), pg_name(name)
{
}
@@ -486,7 +517,7 @@ struct portal_group *
conf::add_portal_group(const char *name)
{
auto pair = conf_portal_groups.try_emplace(name,
- std::make_unique<portal_group>(this, name));
+ iscsi_make_portal_group(this, name));
if (!pair.second) {
log_warnx("duplicated portal-group \"%s\"", name);
return (nullptr);
@@ -521,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
{
@@ -531,7 +601,7 @@ portal_group::is_dummy() const
return (false);
}
-static freebsd::addrinfo_up
+freebsd::addrinfo_up
parse_addr_port(const char *address, const char *def_port)
{
struct addrinfo hints, *ai;
@@ -599,25 +669,6 @@ portal_group::options() const
}
bool
-portal_group::add_portal(const char *value, bool iser)
-{
- freebsd::addrinfo_up ai = parse_addr_port(value, "3260");
- if (!ai) {
- log_warnx("invalid listen address %s", value);
- return (false);
- }
-
- /*
- * XXX: getaddrinfo(3) may return multiple addresses; we should turn
- * those into multiple portals.
- */
-
- pg_portals.emplace_back(std::make_unique<portal>(this, value, iser,
- std::move(ai)));
- return (true);
-}
-
-bool
portal_group::add_option(const char *name, const char *value)
{
return (option_new(pg_options.get(), name, value));
@@ -627,14 +678,14 @@ bool
portal_group::set_discovery_auth_group(const char *ag_name)
{
if (pg_discovery_auth_group != nullptr) {
- log_warnx("discovery-auth-group for portal-group "
- "\"%s\" specified more than once", name());
+ log_warnx("discovery-auth-group for %s "
+ "\"%s\" specified more than once", keyword(), name());
return (false);
}
pg_discovery_auth_group = pg_conf->find_auth_group(ag_name);
if (pg_discovery_auth_group == nullptr) {
log_warnx("unknown discovery-auth-group \"%s\" "
- "for portal-group \"%s\"", ag_name, name());
+ "for %s \"%s\"", ag_name, keyword(), name());
return (false);
}
return (true);
@@ -644,8 +695,8 @@ bool
portal_group::set_dscp(u_int dscp)
{
if (dscp >= 0x40) {
- log_warnx("invalid DSCP value %u for portal-group \"%s\"",
- dscp, name());
+ log_warnx("invalid DSCP value %u for %s \"%s\"",
+ dscp, keyword(), name());
return (false);
}
@@ -653,39 +704,6 @@ portal_group::set_dscp(u_int dscp)
return (true);
}
-bool
-portal_group::set_filter(const char *str)
-{
- enum discovery_filter filter;
-
- if (strcmp(str, "none") == 0) {
- filter = discovery_filter::NONE;
- } else if (strcmp(str, "portal") == 0) {
- filter = discovery_filter::PORTAL;
- } else if (strcmp(str, "portal-name") == 0) {
- filter = discovery_filter::PORTAL_NAME;
- } else if (strcmp(str, "portal-name-auth") == 0) {
- filter = discovery_filter::PORTAL_NAME_AUTH;
- } else {
- log_warnx("invalid discovery-filter \"%s\" for portal-group "
- "\"%s\"; valid values are \"none\", \"portal\", "
- "\"portal-name\", and \"portal-name-auth\"",
- str, name());
- return (false);
- }
-
- if (pg_discovery_filter != discovery_filter::UNKNOWN &&
- pg_discovery_filter != filter) {
- log_warnx("cannot set discovery-filter to \"%s\" for "
- "portal-group \"%s\"; already has a different "
- "value", str, name());
- return (false);
- }
-
- pg_discovery_filter = filter;
- return (true);
-}
-
void
portal_group::set_foreign()
{
@@ -697,8 +715,8 @@ portal_group::set_offload(const char *offload)
{
if (!pg_offload.empty()) {
log_warnx("cannot set offload to \"%s\" for "
- "portal-group \"%s\"; already defined",
- offload, name());
+ "%s \"%s\"; already defined",
+ offload, keyword(), name());
return (false);
}
@@ -710,8 +728,8 @@ bool
portal_group::set_pcp(u_int pcp)
{
if (pcp > 7) {
- log_warnx("invalid PCP value %u for portal-group \"%s\"",
- pcp, name());
+ log_warnx("invalid PCP value %u for %s \"%s\"",
+ pcp, keyword(), name());
return (false);
}
@@ -724,8 +742,8 @@ portal_group::set_redirection(const char *addr)
{
if (!pg_redirection.empty()) {
log_warnx("cannot set redirection to \"%s\" for "
- "portal-group \"%s\"; already defined",
- addr, name());
+ "%s \"%s\"; already defined",
+ addr, keyword(), name());
return (false);
}
@@ -752,16 +770,17 @@ portal_group::verify(struct conf *conf)
if (!pg_redirection.empty()) {
if (!pg_ports.empty()) {
- log_debugx("portal-group \"%s\" assigned to target, "
- "but configured for redirection", name());
+ log_debugx("%s \"%s\" assigned to target, "
+ "but configured for redirection", keyword(),
+ name());
}
pg_assigned = true;
} else if (!pg_ports.empty()) {
pg_assigned = true;
} else {
if (pg_name != "default")
- log_warnx("portal-group \"%s\" not assigned "
- "to any target", name());
+ log_warnx("%s \"%s\" not assigned "
+ "to any target", keyword(), name());
pg_assigned = false;
}
}
@@ -789,8 +808,8 @@ portal_group::open_sockets(struct conf &oldconf)
return (0);
if (!pg_assigned) {
- log_debugx("not listening on portal-group \"%s\", "
- "not assigned to any target", name());
+ log_debugx("not listening on %s \"%s\", "
+ "not assigned to any target", keyword(), name());
return (0);
}
@@ -818,8 +837,8 @@ portal_group::close_sockets()
for (portal_up &portal : pg_portals) {
if (portal->socket() < 0)
continue;
- log_debugx("closing socket for %s, portal-group \"%s\"",
- portal->listen(), name());
+ log_debugx("closing socket for %s, %s \"%s\"",
+ portal->listen(), keyword(), name());
portal->close();
}
}
@@ -1111,8 +1130,8 @@ conf::add_port(struct target *target, struct portal_group *pg, auth_group_sp ag)
{
std::string name = freebsd::stringf("%s-%s", pg->name(),
target->name());
- const auto &pair = conf_ports.try_emplace(name,
- std::make_unique<portal_group_port>(target, pg, ag));
+ const auto &pair = conf_ports.try_emplace(name, pg->create_port(target,
+ ag));
if (!pair.second) {
log_warnx("duplicate port \"%s\"", name.c_str());
return (false);
@@ -1127,8 +1146,8 @@ conf::add_port(struct target *target, struct portal_group *pg,
{
std::string name = freebsd::stringf("%s-%s", pg->name(),
target->name());
- const auto &pair = conf_ports.try_emplace(name,
- std::make_unique<portal_group_port>(target, pg, ctl_port));
+ const auto &pair = conf_ports.try_emplace(name, pg->create_port(target,
+ ctl_port));
if (!pair.second) {
log_warnx("duplicate port \"%s\"", name.c_str());
return (false);
@@ -1185,6 +1204,46 @@ portal_group::find_port(std::string_view target) const
}
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)
+{
+ t_label = freebsd::stringf("%s \"%s\"", keyword, t_name.c_str());
+}
+
+struct target *
conf::add_target(const char *name)
{
if (!valid_iscsi_name(name, log_warnx))
@@ -1198,7 +1257,7 @@ conf::add_target(const char *name)
c = tolower(c);
auto const &pair = conf_targets.try_emplace(t_name,
- std::make_unique<target>(this, t_name));
+ iscsi_make_target(this, t_name));
if (!pair.second) {
log_warnx("duplicated target \"%s\"", name);
return (NULL);
@@ -1225,13 +1284,12 @@ target::use_private_auth(const char *keyword)
return (true);
if (t_auth_group != nullptr) {
- log_warnx("cannot use both auth-group and %s for target \"%s\"",
- keyword, name());
+ log_warnx("cannot use both auth-group and %s for %s",
+ keyword, label());
return (false);
}
- std::string label = freebsd::stringf("target \"%s\"", name());
- t_auth_group = std::make_shared<struct auth_group>(label);
+ t_auth_group = std::make_shared<struct auth_group>(t_label);
t_private_auth = true;
return (true);
}
@@ -1254,40 +1312,24 @@ target::add_chap_mutual(const char *user, const char *secret,
}
bool
-target::add_initiator_name(std::string_view name)
-{
- if (!use_private_auth("initiator-name"))
- return (false);
- return (t_auth_group->add_initiator_name(name));
-}
-
-bool
-target::add_initiator_portal(const char *addr)
-{
- if (!use_private_auth("initiator-portal"))
- return (false);
- return (t_auth_group->add_initiator_portal(addr));
-}
-
-bool
-target::add_lun(u_int id, const char *lun_name)
+target::add_lun(u_int id, const char *lun_label, const char *lun_name)
{
struct lun *t_lun;
if (id >= MAX_LUNS) {
- log_warnx("LUN %u too big for target \"%s\"", id, name());
+ log_warnx("%s too big for %s", lun_label, label());
return (false);
}
if (t_luns[id] != NULL) {
- log_warnx("duplicate LUN %u for target \"%s\"", id, name());
+ log_warnx("duplicate %s for %s", lun_label, label());
return (false);
}
t_lun = t_conf->find_lun(lun_name);
if (t_lun == NULL) {
- log_warnx("unknown LUN named %s used for target \"%s\"",
- lun_name, name());
+ log_warnx("unknown LUN named %s used for %s", lun_name,
+ label());
return (false);
}
@@ -1296,41 +1338,10 @@ target::add_lun(u_int id, const char *lun_name)
}
bool
-target::add_portal_group(const char *pg_name, const char *ag_name)
-{
- struct portal_group *pg;
- auth_group_sp ag;
-
- pg = t_conf->find_portal_group(pg_name);
- if (pg == NULL) {
- log_warnx("unknown portal-group \"%s\" for target \"%s\"",
- pg_name, name());
- return (false);
- }
-
- if (ag_name != NULL) {
- ag = t_conf->find_auth_group(ag_name);
- if (ag == NULL) {
- log_warnx("unknown auth-group \"%s\" for target \"%s\"",
- ag_name, name());
- return (false);
- }
- }
-
- if (!t_conf->add_port(this, pg, std::move(ag))) {
- log_warnx("can't link portal-group \"%s\" to target \"%s\"",
- pg_name, name());
- return (false);
- }
- return (true);
-}
-
-bool
target::set_alias(std::string_view alias)
{
if (has_alias()) {
- log_warnx("alias for target \"%s\" specified more than once",
- name());
+ log_warnx("alias for %s specified more than once", label());
return (false);
}
t_alias = alias;
@@ -1343,16 +1354,16 @@ target::set_auth_group(const char *ag_name)
if (t_auth_group != nullptr) {
if (t_private_auth)
log_warnx("cannot use both auth-group and explicit "
- "authorisations for target \"%s\"", name());
+ "authorisations for %s", label());
else
- log_warnx("auth-group for target \"%s\" "
- "specified more than once", name());
+ log_warnx("auth-group for %s "
+ "specified more than once", label());
return (false);
}
t_auth_group = t_conf->find_auth_group(ag_name);
if (t_auth_group == nullptr) {
- log_warnx("unknown auth-group \"%s\" for target \"%s\"",
- ag_name, name());
+ log_warnx("unknown auth-group \"%s\" for %s",
+ ag_name, label());
return (false);
}
return (true);
@@ -1383,8 +1394,8 @@ target::set_redirection(const char *addr)
{
if (!t_redirection.empty()) {
log_warnx("cannot set redirection to \"%s\" for "
- "target \"%s\"; already defined",
- addr, name());
+ "%s; already defined",
+ addr, label());
return (false);
}
@@ -1393,28 +1404,23 @@ target::set_redirection(const char *addr)
}
struct lun *
-target::start_lun(u_int id)
+target::start_lun(u_int id, const char *lun_label, const char *lun_name)
{
- struct lun *new_lun;
-
if (id >= MAX_LUNS) {
- log_warnx("LUN %u too big for target \"%s\"", id,
- name());
+ log_warnx("%s too big for %s", lun_label, label());
return (nullptr);
}
if (t_luns[id] != NULL) {
- log_warnx("duplicate LUN %u for target \"%s\"", id,
- name());
+ log_warnx("duplicate %s for %s", lun_label, label());
return (nullptr);
}
- std::string lun_name = freebsd::stringf("%s,lun,%u", name(), id);
- new_lun = t_conf->add_lun(lun_name.c_str());
+ struct lun *new_lun = t_conf->add_lun(lun_name);
if (new_lun == nullptr)
return (nullptr);
- new_lun->set_scsiname(lun_name.c_str());
+ new_lun->set_scsiname(lun_name);
t_luns[id] = new_lun;
@@ -1449,7 +1455,7 @@ target::verify()
assert(t_auth_group != nullptr);
}
if (t_ports.empty()) {
- struct portal_group *pg = t_conf->find_portal_group("default");
+ struct portal_group *pg = default_portal_group();
assert(pg != NULL);
t_conf->add_port(this, pg, nullptr);
}
@@ -1457,10 +1463,10 @@ target::verify()
bool found = std::any_of(t_luns.begin(), t_luns.end(),
[](struct lun *lun) { return (lun != nullptr); });
if (!found && t_redirection.empty())
- log_warnx("no LUNs defined for target \"%s\"", name());
+ log_warnx("no LUNs defined for %s", label());
if (found && !t_redirection.empty())
- log_debugx("target \"%s\" contains luns, but configured "
- "for redirection", name());
+ log_debugx("%s contains LUNs, but configured "
+ "for redirection", label());
}
lun::lun(struct conf *conf, std::string_view name)
@@ -1485,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 *
@@ -1714,59 +1722,6 @@ option_new(nvlist_t *nvl, const char *name, const char *value)
return (true);
}
-#ifdef ICL_KERNEL_PROXY
-
-static void
-pdu_receive_proxy(struct pdu *pdu)
-{
- struct connection *conn;
- size_t len;
-
- assert(proxy_mode);
- conn = pdu->pdu_connection;
-
- kernel_receive(pdu);
-
- len = pdu_ahs_length(pdu);
- if (len > 0)
- log_errx(1, "protocol error: non-empty AHS");
-
- len = pdu_data_segment_length(pdu);
- assert(len <= (size_t)conn->conn_max_recv_data_segment_length);
- pdu->pdu_data_len = len;
-}
-
-static void
-pdu_send_proxy(struct pdu *pdu)
-{
-
- assert(proxy_mode);
-
- pdu_set_data_segment_length(pdu, pdu->pdu_data_len);
- kernel_send(pdu);
-}
-
-#endif /* ICL_KERNEL_PROXY */
-
-static void
-pdu_fail(const struct connection *conn __unused, const char *reason __unused)
-{
-}
-
-ctld_connection::ctld_connection(struct portal *portal, int fd,
- const char *host, const struct sockaddr *client_sa) :
- conn_portal(portal), conn_initiator_addr(host),
- conn_initiator_sa(client_sa)
-{
- connection_init(&conn, &conn_ops, proxy_mode);
- conn.conn_socket = fd;
-}
-
-ctld_connection::~ctld_connection()
-{
- chap_delete(conn_chap);
-}
-
bool
lun::verify()
{
@@ -1838,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" ||
@@ -1882,23 +1843,23 @@ portal::init_socket()
struct portal_group *pg = portal_group();
struct kevent kev;
freebsd::fd_up s;
- int error, sockbuf;
+ int error;
int one = 1;
#ifdef ICL_KERNEL_PROXY
if (proxy_mode) {
int id = pg->conf()->add_proxy_portal(this);
- log_debugx("listening on %s, portal-group \"%s\", "
- "portal id %d, using ICL proxy", listen(), pg->pg_name,
- id);
- kernel_listen(ai(), p_iser, id);
+ log_debugx("listening on %s, %s \"%s\", "
+ "portal id %d, using ICL proxy", listen(), pg->keyword(),
+ pg->name(), id);
+ kernel_listen(ai(), protocol() == ISER, id);
return (true);
}
#endif
assert(proxy_mode == false);
- assert(p_iser == false);
+ assert(protocol() != portal_protocol::ISER);
- log_debugx("listening on %s, portal-group \"%s\"", listen(),
+ log_debugx("listening on %s, %s \"%s\"", listen(), pg->keyword(),
pg->name());
s = ::socket(p_ai->ai_family, p_ai->ai_socktype, p_ai->ai_protocol);
if (!s) {
@@ -1906,14 +1867,6 @@ portal::init_socket()
return (false);
}
- sockbuf = SOCKBUF_SIZE;
- if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &sockbuf,
- sizeof(sockbuf)) == -1)
- log_warn("setsockopt(SO_RCVBUF) failed for %s", listen());
- sockbuf = SOCKBUF_SIZE;
- if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sockbuf,
- sizeof(sockbuf)) == -1)
- log_warn("setsockopt(SO_SNDBUF) failed for %s", listen());
if (setsockopt(s, SOL_SOCKET, SO_NO_DDP, &one,
sizeof(one)) == -1)
log_warn("setsockopt(SO_NO_DDP) failed for %s", listen());
@@ -1960,6 +1913,9 @@ portal::init_socket()
}
}
+ if (!init_socket_options(s))
+ return (false);
+
error = bind(s, p_ai->ai_addr, p_ai->ai_addrlen);
if (error != 0) {
log_warn("bind(2) failed for %s", listen());
@@ -1989,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);
}
@@ -2038,7 +2000,18 @@ conf::apply(struct conf *oldconf)
if (it != oldconf->conf_portal_groups.end())
newpg.set_tag(it->second->tag());
else
- newpg.set_tag(++last_portal_group_tag);
+ 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. */
@@ -2203,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.
@@ -2210,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) {
@@ -2225,7 +2204,7 @@ conf::apply(struct conf *oldconf)
return (cumulated_error);
}
-static bool
+bool
timed_out(void)
{
@@ -2342,7 +2321,7 @@ wait_for_children(bool block)
}
static void
-handle_connection(struct portal *portal, int fd,
+handle_connection(struct portal *portal, freebsd::fd_up fd,
const struct sockaddr *client_sa, bool dont_fork)
{
struct portal_group *pg;
@@ -2373,10 +2352,8 @@ handle_connection(struct portal *portal, int fd,
pid = fork();
if (pid < 0)
log_err(1, "fork");
- if (pid > 0) {
- close(fd);
+ if (pid > 0)
return;
- }
conf->close_pidfile();
}
@@ -2390,17 +2367,7 @@ handle_connection(struct portal *portal, int fd,
log_set_peer_addr(host);
setproctitle("%s", host);
- ctld_connection conn(portal, fd, host, client_sa);
- start_timer(conf->timeout(), true);
- kernel_capsicate();
- conn.login();
- if (conn.session_type() == CONN_SESSION_TYPE_NORMAL) {
- conn.kernel_handoff();
- log_debugx("connection handed off to the kernel");
- } else {
- assert(conn.session_type() == CONN_SESSION_TYPE_DISCOVERY);
- conn.discovery();
- }
+ portal->handle_connection(std::move(fd), host, client_sa);
log_debugx("nothing more to do; exiting");
exit(0);
}
@@ -2585,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);
@@ -2612,8 +2582,15 @@ conf_new_from_file(const char *path, bool ucl)
"going with defaults");
pg = conf->find_portal_group("default");
assert(pg != NULL);
- pg->add_portal("0.0.0.0", false);
- pg->add_portal("[::]", false);
+ 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()) {
@@ -2644,7 +2621,7 @@ conf::add_pports(struct kports &kports)
if (ret > 0) {
if (!add_port(kports, targ, i_pp, i_vp)) {
log_warnx("can't create new ioctl port "
- "for target \"%s\"", targ->name());
+ "for %s", targ->label());
return (false);
}
@@ -2653,19 +2630,19 @@ conf::add_pports(struct kports &kports)
pp = kports.find_port(targ->pport());
if (pp == NULL) {
- log_warnx("unknown port \"%s\" for target \"%s\"",
- targ->pport(), targ->name());
+ log_warnx("unknown port \"%s\" for %s",
+ targ->pport(), targ->label());
return (false);
}
if (pp->linked()) {
- log_warnx("can't link port \"%s\" to target \"%s\", "
+ log_warnx("can't link port \"%s\" to %s, "
"port already linked to some target",
- targ->pport(), targ->name());
+ targ->pport(), targ->label());
return (false);
}
if (!add_port(targ, pp)) {
- log_warnx("can't link port \"%s\" to target \"%s\"",
- targ->pport(), targ->name());
+ log_warnx("can't link port \"%s\" to %s",
+ targ->pport(), targ->label());
return (false);
}
}
diff --git a/usr.sbin/ctld/ctld.hh b/usr.sbin/ctld/ctld.hh
index 059f719668f9..bfe4507bb3e6 100644
--- a/usr.sbin/ctld/ctld.hh
+++ b/usr.sbin/ctld/ctld.hh
@@ -56,7 +56,6 @@
#define DEFAULT_CD_BLOCKSIZE 2048
#define MAX_LUNS 1024
-#define SOCKBUF_SIZE 1048576
struct isns_req;
struct port;
@@ -111,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;
@@ -124,24 +129,38 @@ 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_names;
- std::list<auth_portal> ag_portals;
+ 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;
};
using auth_group_sp = std::shared_ptr<auth_group>;
+enum class portal_protocol {
+ ISCSI,
+ ISER,
+ NVME_TCP,
+ NVME_DISCOVERY_TCP,
+};
+
struct portal {
- portal(struct portal_group *pg, std::string_view listen, bool iser,
- freebsd::addrinfo_up ai) :
+ portal(struct portal_group *pg, std::string_view listen,
+ portal_protocol protocol, freebsd::addrinfo_up ai) :
p_portal_group(pg), p_listen(listen), p_ai(std::move(ai)),
- p_iser(iser) {}
+ p_protocol(protocol) {}
+ virtual ~portal() = default;
bool reuse_socket(portal &oldp);
bool init_socket();
+ virtual bool init_socket_options(int s __unused) { return true; }
+ 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; }
+ struct 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; }
int socket() const { return p_socket; }
void close() { p_socket.reset(); }
@@ -149,12 +168,13 @@ private:
struct portal_group *p_portal_group;
std::string p_listen;
freebsd::addrinfo_up p_ai;
- bool p_iser;
+ portal_protocol p_protocol;
freebsd::fd_up p_socket;
};
using portal_up = std::unique_ptr<portal>;
+using port_up = std::unique_ptr<port>;
enum class discovery_filter {
UNKNOWN,
@@ -166,15 +186,17 @@ enum class discovery_filter {
struct portal_group {
portal_group(struct conf *conf, std::string_view name);
+ virtual ~portal_group() = default;
struct conf *conf() const { return pg_conf; }
+ virtual const char *keyword() const = 0;
const char *name() const { return pg_name.c_str(); }
bool assigned() const { return pg_assigned; }
bool is_dummy() const;
bool is_redirecting() const { return !pg_redirection.empty(); }
struct auth_group *discovery_auth_group() const
{ return pg_discovery_auth_group.get(); }
- discovery_filter discovery_filter() const
+ enum discovery_filter discovery_filter() const
{ return pg_discovery_filter; }
int dscp() const { return pg_dscp; }
const char *offload() const { return pg_offload.c_str(); }
@@ -188,17 +210,25 @@ struct portal_group {
const std::unordered_map<std::string, port *> &ports() const
{ return pg_ports; }
- bool add_portal(const char *value, bool iser);
+ virtual void allocate_tag() = 0;
+ virtual bool add_portal(const char *value,
+ portal_protocol protocol) = 0;
+ virtual void add_default_portals() = 0;
bool add_option(const char *name, const char *value);
bool set_discovery_auth_group(const char *name);
bool set_dscp(u_int dscp);
- bool set_filter(const char *str);
+ virtual bool set_filter(const char *str) = 0;
void set_foreign();
bool set_offload(const char *offload);
bool set_pcp(u_int pcp);
bool set_redirection(const char *addr);
void set_tag(uint16_t tag);
+ virtual port_up create_port(struct target *target, auth_group_sp ag) =
+ 0;
+ virtual port_up create_port(struct target *target, uint32_t ctl_port) =
+ 0;
+
void add_port(struct portal_group_port *port);
const struct port *find_port(std::string_view target) const;
void remove_port(struct portal_group_port *port);
@@ -208,9 +238,10 @@ struct portal_group {
int open_sockets(struct conf &oldconf);
void close_sockets();
-private:
+protected:
struct conf *pg_conf;
freebsd::nvlist_up pg_options;
+ const char *pg_keyword;
std::string pg_name;
auth_group_sp pg_discovery_auth_group;
enum discovery_filter pg_discovery_filter =
@@ -254,7 +285,7 @@ protected:
uint32_t p_ctl_port = 0;
};
-struct portal_group_port final : public port {
+struct portal_group_port : public port {
portal_group_port(struct target *target, struct portal_group *pg,
auth_group_sp ag);
portal_group_port(struct target *target, struct portal_group *pg,
@@ -270,10 +301,7 @@ struct portal_group_port final : public port {
void clear_references() override;
- bool kernel_create_port() override;
- bool kernel_remove_port() override;
-
-private:
+protected:
auth_group_sp p_auth_group;
struct portal_group *p_portal_group;
};
@@ -348,14 +376,15 @@ private:
};
struct target {
- target(struct conf *conf, std::string_view name) :
- t_conf(conf), t_name(name) {}
+ target(struct conf *conf, const char *keyword, std::string_view name);
+ virtual ~target() = default;
bool has_alias() const { return !t_alias.empty(); }
bool has_pport() const { return !t_pport.empty(); }
bool has_redirection() const { return !t_redirection.empty(); }
const char *alias() const { return t_alias.c_str(); }
const char *name() const { return t_name.c_str(); }
+ const char *label() const { return t_label.c_str(); }
const char *pport() const { return t_pport.c_str(); }
bool private_auth() const { return t_private_auth; }
const char *redirection() const { return t_redirection.c_str(); }
@@ -367,30 +396,40 @@ 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);
- bool add_initiator_name(std::string_view name);
- bool add_initiator_portal(const char *addr);
- bool add_lun(u_int id, const char *lun_name);
- bool add_portal_group(const char *pg_name, const char *ag_name);
+ 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);
bool set_auth_group(const char *ag_name);
bool set_auth_type(const char *type);
bool set_physical_port(std::string_view pport);
bool set_redirection(const char *addr);
- struct lun *start_lun(u_int id);
+ 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);
void remove_port(struct port *port);
void verify();
-private:
+protected:
bool use_private_auth(const char *keyword);
+ bool add_lun(u_int id, const char *lun_label, const char *lun_name);
+ struct lun *start_lun(u_int id, const char *lun_label,
+ const char *lun_name);
+ virtual struct portal_group *default_portal_group() = 0;
struct conf *t_conf;
std::array<struct lun *, MAX_LUNS> t_luns;
auth_group_sp t_auth_group;
std::list<port *> t_ports;
std::string t_name;
+ std::string t_label;
std::string t_alias;
std::string t_redirection;
/* Name of this target's physical port, if any, i.e. "isp0" */
@@ -398,6 +437,8 @@ private:
bool t_private_auth;
};
+using target_up = std::unique_ptr<target>;
+
struct isns {
isns(std::string_view addr, freebsd::addrinfo_up ai) :
i_addr(addr), i_ai(std::move(ai)) {}
@@ -413,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();
@@ -429,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,
@@ -438,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);
@@ -474,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;
@@ -485,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 *);
@@ -528,50 +587,7 @@ private:
std::unordered_map<std::string, struct pport> pports;
};
-#define CONN_SESSION_TYPE_NONE 0
-#define CONN_SESSION_TYPE_DISCOVERY 1
-#define CONN_SESSION_TYPE_NORMAL 2
-
-struct ctld_connection {
- ctld_connection(struct portal *portal, int fd, const char *host,
- const struct sockaddr *client_sa);
- ~ctld_connection();
-
- int session_type() const { return conn_session_type; }
-
- void login();
- void discovery();
- void kernel_handoff();
-private:
- void login_chap(struct auth_group *ag);
- void login_negotiate_key(struct pdu *request, const char *name,
- const char *value, bool skipped_security,
- struct keys *response_keys);
- bool login_portal_redirect(struct pdu *request);
- bool login_target_redirect(struct pdu *request);
- void login_negotiate(struct pdu *request);
- void login_wait_transition();
-
- bool discovery_target_filtered_out(const struct port *port) const;
-
- struct connection conn;
- struct portal *conn_portal = nullptr;
- const struct port *conn_port = nullptr;
- struct target *conn_target = nullptr;
- int conn_session_type = CONN_SESSION_TYPE_NONE;
- std::string conn_initiator_name;
- std::string conn_initiator_addr;
- std::string conn_initiator_alias;
- uint8_t conn_initiator_isid[6];
- const struct sockaddr *conn_initiator_sa = nullptr;
- int conn_max_recv_data_segment_limit = 0;
- int conn_max_send_data_segment_limit = 0;
- int conn_max_burst_limit = 0;
- int conn_first_burst_limit = 0;
- std::string conn_user;
- struct chap *conn_chap = nullptr;
-};
-
+extern bool proxy_mode;
extern int ctl_fd;
bool parse_conf(const char *path);
@@ -584,6 +600,9 @@ void conf_start(struct conf *new_conf);
bool option_new(nvlist_t *nvl,
const char *name, const char *value);
+freebsd::addrinfo_up parse_addr_port(const char *address,
+ const char *def_port);
+
void kernel_init(void);
void kernel_capsicate(void);
@@ -597,7 +616,22 @@ void kernel_send(struct pdu *pdu);
void kernel_receive(struct pdu *pdu);
#endif
+bool ctl_create_port(const char *driver,
+ const nvlist_t *nvl, uint32_t *ctl_port);
+bool ctl_remove_port(const char *driver, nvlist_t *nvl);
+
+portal_group_up iscsi_make_portal_group(struct conf *conf,
+ std::string_view name);
+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();
+bool timed_out();
#endif /* !__CTLD_HH__ */
diff --git a/usr.sbin/ctld/discovery.cc b/usr.sbin/ctld/discovery.cc
index 3c9f1cfa1dac..8f6d371b696d 100644
--- a/usr.sbin/ctld/discovery.cc
+++ b/usr.sbin/ctld/discovery.cc
@@ -39,6 +39,7 @@
#include <sys/socket.h>
#include "ctld.hh"
+#include "iscsi.hh"
#include "iscsi_proto.h"
static struct pdu *
@@ -112,6 +113,9 @@ discovery_add_target(struct keys *response_keys, const struct target *targ)
if (pg == nullptr)
continue;
for (const portal_up &portal : pg->portals()) {
+ if (portal->protocol() != portal_protocol::ISCSI &&
+ portal->protocol() != portal_protocol::ISER)
+ continue;
ai = portal->ai();
ret = getnameinfo(ai->ai_addr, ai->ai_addrlen,
hbuf, sizeof(hbuf), sbuf, sizeof(sbuf),
@@ -145,7 +149,7 @@ discovery_add_target(struct keys *response_keys, const struct target *targ)
}
bool
-ctld_connection::discovery_target_filtered_out(const struct port *port) const
+iscsi_connection::discovery_target_filtered_out(const struct port *port) const
{
const struct auth_group *ag;
const struct portal_group *pg;
@@ -208,7 +212,7 @@ ctld_connection::discovery_target_filtered_out(const struct port *port) const
}
void
-ctld_connection::discovery()
+iscsi_connection::discovery()
{
struct pdu *request, *response;
struct keys *request_keys, *response_keys;
diff --git a/usr.sbin/ctld/iscsi.cc b/usr.sbin/ctld/iscsi.cc
new file mode 100644
index 000000000000..bee036b95bf2
--- /dev/null
+++ b/usr.sbin/ctld/iscsi.cc
@@ -0,0 +1,508 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2003, 2004 Silicon Graphics International Corp.
+ * Copyright (c) 1997-2007 Kenneth D. Merry
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * Copyright (c) 2017 Jakub Wojciech Klama <jceel@FreeBSD.org>
+ * All rights reserved.
+ * Copyright (c) 2025 Chelsio Communications, Inc.
+ *
+ * Portions of this software were developed by Edward Tomasz Napierala
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon
+ * including a substantially similar Disclaimer requirement for further
+ * binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ */
+
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/time.h>
+#include <assert.h>
+#include <libiscsiutil.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <cam/ctl/ctl.h>
+#include <cam/ctl/ctl_io.h>
+#include <cam/ctl/ctl_ioctl.h>
+
+#include "ctld.hh"
+#include "iscsi.hh"
+
+#define SOCKBUF_SIZE 1048576
+
+struct iscsi_portal final : public portal {
+ iscsi_portal(struct portal_group *pg, const char *listen,
+ portal_protocol protocol, freebsd::addrinfo_up ai) :
+ portal(pg, listen, protocol, std::move(ai)) {}
+
+ bool init_socket_options(int s) override;
+ void handle_connection(freebsd::fd_up fd, const char *host,
+ const struct sockaddr *client_sa) override;
+};
+
+struct iscsi_portal_group final : public portal_group {
+ iscsi_portal_group(struct conf *conf, std::string_view name) :
+ portal_group(conf, name) {}
+
+ const char *keyword() const override
+ { return "portal-group"; }
+
+ void allocate_tag() override;
+ bool add_portal(const char *value, portal_protocol protocol)
+ override;
+ void add_default_portals() override;
+ bool set_filter(const char *str) override;
+
+ virtual port_up create_port(struct target *target, auth_group_sp ag)
+ override;
+ virtual port_up create_port(struct target *target, uint32_t ctl_port)
+ override;
+private:
+ static uint16_t last_portal_group_tag;
+};
+
+struct iscsi_port final : public portal_group_port {
+ iscsi_port(struct target *target, struct portal_group *pg,
+ auth_group_sp ag) :
+ portal_group_port(target, pg, ag) {}
+ iscsi_port(struct target *target, struct portal_group *pg,
+ uint32_t ctl_port) :
+ portal_group_port(target, pg, ctl_port) {}
+
+ bool kernel_create_port() override;
+ bool kernel_remove_port() override;
+
+private:
+ static bool module_loaded;
+ static void load_kernel_module();
+};
+
+struct iscsi_target final : public target {
+ iscsi_target(struct conf *conf, std::string_view name) :
+ target(conf, "target", name) {}
+
+ bool add_initiator_name(std::string_view name) override;
+ bool add_initiator_portal(const char *addr) override;
+ bool add_lun(u_int id, const char *lun_name) override;
+ bool add_portal_group(const char *pg_name, const char *ag_name)
+ override;
+ struct lun *start_lun(u_int id) override;
+
+protected:
+ struct portal_group *default_portal_group() override;
+};
+
+#ifdef ICL_KERNEL_PROXY
+static void pdu_receive_proxy(struct pdu *pdu);
+static void pdu_send_proxy(struct pdu *pdu);
+#endif /* ICL_KERNEL_PROXY */
+static void pdu_fail(const struct connection *conn, const char *reason);
+
+uint16_t iscsi_portal_group::last_portal_group_tag = 0xff;
+bool iscsi_port::module_loaded = false;
+
+static struct connection_ops conn_ops = {
+ .timed_out = timed_out,
+#ifdef ICL_KERNEL_PROXY
+ .pdu_receive_proxy = pdu_receive_proxy,
+ .pdu_send_proxy = pdu_send_proxy,
+#else
+ .pdu_receive_proxy = nullptr,
+ .pdu_send_proxy = nullptr,
+#endif
+ .fail = pdu_fail,
+};
+
+portal_group_up
+iscsi_make_portal_group(struct conf *conf, std::string_view name)
+{
+ return std::make_unique<iscsi_portal_group>(conf, name);
+}
+
+target_up
+iscsi_make_target(struct conf *conf, std::string_view name)
+{
+ return std::make_unique<iscsi_target>(conf, name);
+}
+
+void
+iscsi_portal_group::allocate_tag()
+{
+ set_tag(++last_portal_group_tag);
+}
+
+bool
+iscsi_portal_group::add_portal(const char *value, portal_protocol protocol)
+{
+ switch (protocol) {
+ case portal_protocol::ISCSI:
+ case portal_protocol::ISER:
+ break;
+ default:
+ log_warnx("unsupported portal protocol for %s", value);
+ return (false);
+ }
+
+ freebsd::addrinfo_up ai = parse_addr_port(value, "3260");
+ if (!ai) {
+ log_warnx("invalid listen address %s", value);
+ return (false);
+ }
+
+ /*
+ * XXX: getaddrinfo(3) may return multiple addresses; we should turn
+ * those into multiple portals.
+ */
+
+ pg_portals.emplace_back(std::make_unique<iscsi_portal>(this, value,
+ protocol, std::move(ai)));
+ return (true);
+}
+
+void
+iscsi_portal_group::add_default_portals()
+{
+ add_portal("0.0.0.0", portal_protocol::ISCSI);
+ add_portal("[::]", portal_protocol::ISCSI);
+}
+
+bool
+iscsi_portal_group::set_filter(const char *str)
+{
+ enum discovery_filter filter;
+
+ if (strcmp(str, "none") == 0) {
+ filter = discovery_filter::NONE;
+ } else if (strcmp(str, "portal") == 0) {
+ filter = discovery_filter::PORTAL;
+ } else if (strcmp(str, "portal-name") == 0) {
+ filter = discovery_filter::PORTAL_NAME;
+ } else if (strcmp(str, "portal-name-auth") == 0) {
+ filter = discovery_filter::PORTAL_NAME_AUTH;
+ } else {
+ log_warnx("invalid discovery-filter \"%s\" for portal-group "
+ "\"%s\"; valid values are \"none\", \"portal\", "
+ "\"portal-name\", and \"portal-name-auth\"",
+ str, name());
+ return (false);
+ }
+
+ if (pg_discovery_filter != discovery_filter::UNKNOWN &&
+ pg_discovery_filter != filter) {
+ log_warnx("cannot set discovery-filter to \"%s\" for "
+ "portal-group \"%s\"; already has a different "
+ "value", str, name());
+ return (false);
+ }
+
+ pg_discovery_filter = filter;
+ return (true);
+}
+
+port_up
+iscsi_portal_group::create_port(struct target *target, auth_group_sp ag)
+{
+ return std::make_unique<iscsi_port>(target, this, ag);
+}
+
+port_up
+iscsi_portal_group::create_port(struct target *target, uint32_t ctl_port)
+{
+ return std::make_unique<iscsi_port>(target, this, ctl_port);
+}
+
+void
+iscsi_port::load_kernel_module()
+{
+ int saved_errno;
+
+ if (module_loaded)
+ return;
+
+ saved_errno = errno;
+ if (modfind("cfiscsi") == -1 && kldload("cfiscsi") == -1)
+ log_warn("couldn't load cfiscsi");
+ errno = saved_errno;
+ module_loaded = true;
+}
+
+bool
+iscsi_port::kernel_create_port()
+{
+ struct portal_group *pg = p_portal_group;
+ struct target *targ = p_target;
+
+ load_kernel_module();
+
+ freebsd::nvlist_up nvl = pg->options();
+ nvlist_add_string(nvl.get(), "cfiscsi_target", targ->name());
+ nvlist_add_string(nvl.get(), "ctld_portal_group_name", pg->name());
+ nvlist_add_stringf(nvl.get(), "cfiscsi_portal_group_tag", "%u",
+ pg->tag());
+
+ if (targ->has_alias()) {
+ nvlist_add_string(nvl.get(), "cfiscsi_target_alias",
+ targ->alias());
+ }
+
+ return (ctl_create_port("iscsi", nvl.get(), &p_ctl_port));
+}
+
+bool
+iscsi_port::kernel_remove_port()
+{
+ freebsd::nvlist_up nvl(nvlist_create(0));
+ nvlist_add_string(nvl.get(), "cfiscsi_target", p_target->name());
+ nvlist_add_stringf(nvl.get(), "cfiscsi_portal_group_tag", "%u",
+ p_portal_group->tag());
+
+ return (ctl_remove_port("iscsi", nvl.get()));
+}
+
+bool
+iscsi_portal::init_socket_options(int s)
+{
+ int sockbuf;
+
+ sockbuf = SOCKBUF_SIZE;
+ if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &sockbuf,
+ sizeof(sockbuf)) == -1) {
+ log_warn("setsockopt(SO_RCVBUF) failed for %s", listen());
+ return (false);
+ }
+ sockbuf = SOCKBUF_SIZE;
+ if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sockbuf,
+ sizeof(sockbuf)) == -1) {
+ log_warn("setsockopt(SO_SNDBUF) failed for %s", listen());
+ return (false);
+ }
+ return (true);
+}
+
+bool
+iscsi_target::add_initiator_name(std::string_view name)
+{
+ if (!use_private_auth("initiator-name"))
+ return (false);
+ return (t_auth_group->add_initiator_name(name));
+}
+
+bool
+iscsi_target::add_initiator_portal(const char *addr)
+{
+ if (!use_private_auth("initiator-portal"))
+ return (false);
+ return (t_auth_group->add_initiator_portal(addr));
+}
+
+bool
+iscsi_target::add_lun(u_int id, const char *lun_name)
+{
+ std::string lun_label = "LUN " + std::to_string(id);
+ return target::add_lun(id, lun_label.c_str(), lun_name);
+}
+
+bool
+iscsi_target::add_portal_group(const char *pg_name, const char *ag_name)
+{
+ struct portal_group *pg;
+ auth_group_sp ag;
+
+ pg = t_conf->find_portal_group(pg_name);
+ if (pg == NULL) {
+ log_warnx("unknown portal-group \"%s\" for %s", pg_name,
+ label());
+ return (false);
+ }
+
+ if (ag_name != NULL) {
+ ag = t_conf->find_auth_group(ag_name);
+ if (ag == NULL) {
+ log_warnx("unknown auth-group \"%s\" for %s", ag_name,
+ label());
+ return (false);
+ }
+ }
+
+ if (!t_conf->add_port(this, pg, std::move(ag))) {
+ log_warnx("can't link portal-group \"%s\" to %s", pg_name,
+ label());
+ return (false);
+ }
+ return (true);
+}
+
+struct lun *
+iscsi_target::start_lun(u_int id)
+{
+ std::string lun_label = "LUN " + std::to_string(id);
+ std::string lun_name = freebsd::stringf("%s,lun,%u", name(), id);
+ return target::start_lun(id, lun_label.c_str(), lun_name.c_str());
+}
+
+struct portal_group *
+iscsi_target::default_portal_group()
+{
+ return t_conf->find_portal_group("default");
+}
+
+#ifdef ICL_KERNEL_PROXY
+
+static void
+pdu_receive_proxy(struct pdu *pdu)
+{
+ struct connection *conn;
+ size_t len;
+
+ assert(proxy_mode);
+ conn = pdu->pdu_connection;
+
+ kernel_receive(pdu);
+
+ len = pdu_ahs_length(pdu);
+ if (len > 0)
+ log_errx(1, "protocol error: non-empty AHS");
+
+ len = pdu_data_segment_length(pdu);
+ assert(len <= (size_t)conn->conn_max_recv_data_segment_length);
+ pdu->pdu_data_len = len;
+}
+
+static void
+pdu_send_proxy(struct pdu *pdu)
+{
+
+ assert(proxy_mode);
+
+ pdu_set_data_segment_length(pdu, pdu->pdu_data_len);
+ kernel_send(pdu);
+}
+
+#endif /* ICL_KERNEL_PROXY */
+
+static void
+pdu_fail(const struct connection *conn __unused, const char *reason __unused)
+{
+}
+
+iscsi_connection::iscsi_connection(struct portal *portal, freebsd::fd_up fd,
+ const char *host, const struct sockaddr *client_sa) :
+ conn_portal(portal), conn_fd(std::move(fd)), conn_initiator_addr(host),
+ conn_initiator_sa(client_sa)
+{
+ connection_init(&conn, &conn_ops, proxy_mode);
+ conn.conn_socket = conn_fd;
+}
+
+iscsi_connection::~iscsi_connection()
+{
+ chap_delete(conn_chap);
+}
+
+void
+iscsi_connection::kernel_handoff()
+{
+ struct portal_group *pg = conn_portal->portal_group();
+ struct ctl_iscsi req;
+
+ bzero(&req, sizeof(req));
+
+ req.type = CTL_ISCSI_HANDOFF;
+ strlcpy(req.data.handoff.initiator_name, conn_initiator_name.c_str(),
+ sizeof(req.data.handoff.initiator_name));
+ strlcpy(req.data.handoff.initiator_addr, conn_initiator_addr.c_str(),
+ sizeof(req.data.handoff.initiator_addr));
+ if (!conn_initiator_alias.empty()) {
+ strlcpy(req.data.handoff.initiator_alias,
+ conn_initiator_alias.c_str(),
+ sizeof(req.data.handoff.initiator_alias));
+ }
+ memcpy(req.data.handoff.initiator_isid, conn_initiator_isid,
+ sizeof(req.data.handoff.initiator_isid));
+ strlcpy(req.data.handoff.target_name, conn_target->name(),
+ sizeof(req.data.handoff.target_name));
+ strlcpy(req.data.handoff.offload, pg->offload(),
+ sizeof(req.data.handoff.offload));
+#ifdef ICL_KERNEL_PROXY
+ if (proxy_mode)
+ req.data.handoff.connection_id = conn.conn_socket;
+ else
+ req.data.handoff.socket = conn.conn_socket;
+#else
+ req.data.handoff.socket = conn.conn_socket;
+#endif
+ req.data.handoff.portal_group_tag = pg->tag();
+ if (conn.conn_header_digest == CONN_DIGEST_CRC32C)
+ req.data.handoff.header_digest = CTL_ISCSI_DIGEST_CRC32C;
+ if (conn.conn_data_digest == CONN_DIGEST_CRC32C)
+ req.data.handoff.data_digest = CTL_ISCSI_DIGEST_CRC32C;
+ req.data.handoff.cmdsn = conn.conn_cmdsn;
+ req.data.handoff.statsn = conn.conn_statsn;
+ req.data.handoff.max_recv_data_segment_length =
+ conn.conn_max_recv_data_segment_length;
+ req.data.handoff.max_send_data_segment_length =
+ conn.conn_max_send_data_segment_length;
+ req.data.handoff.max_burst_length = conn.conn_max_burst_length;
+ req.data.handoff.first_burst_length = conn.conn_first_burst_length;
+ req.data.handoff.immediate_data = conn.conn_immediate_data;
+
+ if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) {
+ log_err(1, "error issuing CTL_ISCSI ioctl; "
+ "dropping connection");
+ }
+
+ if (req.status != CTL_ISCSI_OK) {
+ log_errx(1, "error returned from CTL iSCSI handoff request: "
+ "%s; dropping connection", req.error_str);
+ }
+}
+
+void
+iscsi_connection::handle()
+{
+ login();
+ if (conn_session_type == CONN_SESSION_TYPE_NORMAL) {
+ kernel_handoff();
+ log_debugx("connection handed off to the kernel");
+ } else {
+ assert(conn_session_type == CONN_SESSION_TYPE_DISCOVERY);
+ discovery();
+ }
+}
+
+void
+iscsi_portal::handle_connection(freebsd::fd_up fd, const char *host,
+ const struct sockaddr *client_sa)
+{
+ struct conf *conf = portal_group()->conf();
+
+ iscsi_connection conn(this, std::move(fd), host, client_sa);
+ start_timer(conf->timeout(), true);
+ kernel_capsicate();
+ conn.handle();
+}
diff --git a/usr.sbin/ctld/iscsi.hh b/usr.sbin/ctld/iscsi.hh
new file mode 100644
index 000000000000..d510e8c6731b
--- /dev/null
+++ b/usr.sbin/ctld/iscsi.hh
@@ -0,0 +1,79 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2012 The FreeBSD Foundation
+ *
+ * This software was developed by Edward Tomasz Napierala under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef __ISCSI_HH__
+#define __ISCSI_HH__
+
+#define CONN_SESSION_TYPE_NONE 0
+#define CONN_SESSION_TYPE_DISCOVERY 1
+#define CONN_SESSION_TYPE_NORMAL 2
+
+struct iscsi_connection {
+ iscsi_connection(struct portal *portal, freebsd::fd_up fd,
+ const char *host, const struct sockaddr *client_sa);
+ ~iscsi_connection();
+
+ void handle();
+private:
+ void login();
+ void login_chap(struct auth_group *ag);
+ void login_negotiate_key(struct pdu *request, const char *name,
+ const char *value, bool skipped_security,
+ struct keys *response_keys);
+ bool login_portal_redirect(struct pdu *request);
+ bool login_target_redirect(struct pdu *request);
+ void login_negotiate(struct pdu *request);
+ void login_wait_transition();
+
+ void discovery();
+ bool discovery_target_filtered_out(const struct port *port) const;
+
+ void kernel_handoff();
+
+ struct connection conn;
+ struct portal *conn_portal = nullptr;
+ const struct port *conn_port = nullptr;
+ struct target *conn_target = nullptr;
+ freebsd::fd_up conn_fd;
+ int conn_session_type = CONN_SESSION_TYPE_NONE;
+ std::string conn_initiator_name;
+ std::string conn_initiator_addr;
+ std::string conn_initiator_alias;
+ uint8_t conn_initiator_isid[6];
+ const struct sockaddr *conn_initiator_sa = nullptr;
+ int conn_max_recv_data_segment_limit = 0;
+ int conn_max_send_data_segment_limit = 0;
+ int conn_max_burst_limit = 0;
+ int conn_first_burst_limit = 0;
+ std::string conn_user;
+ struct chap *conn_chap = nullptr;
+};
+
+#endif /* !__ISCSI_HH__ */
diff --git a/usr.sbin/ctld/kernel.cc b/usr.sbin/ctld/kernel.cc
index 39bbb4290e84..6b17ce60ac69 100644
--- a/usr.sbin/ctld/kernel.cc
+++ b/usr.sbin/ctld/kernel.cc
@@ -75,8 +75,6 @@
#define NVLIST_BUFSIZE 1024
-extern bool proxy_mode;
-
int ctl_fd = 0;
void
@@ -108,7 +106,7 @@ kernel_init(void)
/*
* Backend LUN information.
*/
-using attr_list = std::list<std::pair<std::string, std::string>>;
+using attr_list_t = std::list<std::pair<std::string, std::string>>;
struct cctl_lun {
uint64_t lun_id;
@@ -119,7 +117,7 @@ struct cctl_lun {
std::string serial_number;
std::string device_id;
std::string ctld_name;
- attr_list attr_list;
+ attr_list_t attr_list;
};
struct cctl_port {
@@ -128,11 +126,14 @@ struct cctl_port {
std::string port_name;
int pp;
int vp;
+ uint16_t portid;
int cfiscsi_state;
std::string cfiscsi_target;
+ std::string nqn;
uint16_t cfiscsi_portal_group_tag;
std::string ctld_portal_group_name;
- attr_list attr_list;
+ std::string ctld_transport_group_name;
+ attr_list_t attr_list;
};
struct cctl_devlist_data {
@@ -324,6 +325,14 @@ cctl_end_pelement(void *user_data, const char *name)
cur_port->cfiscsi_portal_group_tag = strtoul(str.c_str(), NULL, 0);
} else if (strcmp(name, "ctld_portal_group_name") == 0) {
cur_port->ctld_portal_group_name = std::move(str);
+ } else if (strcmp(name, "ctld_transport_group_name") == 0) {
+ cur_port->ctld_transport_group_name = std::move(str);
+ } else if (strcmp(name, "nqn") == 0) {
+ cur_port->nqn = std::move(str);
+ } else if (strcmp(name, "portid") == 0) {
+ if (str.empty())
+ log_errx(1, "%s: %s missing its argument", __func__, name);
+ cur_port->portid = strtoul(str.c_str(), NULL, 0);
} else if (strcmp(name, "targ_port") == 0) {
devlist->cur_port = NULL;
} else if (strcmp(name, "ctlportlist") == 0) {
@@ -432,6 +441,93 @@ retry_port:
return (true);
}
+void
+add_iscsi_port(struct kports &kports, struct conf *conf,
+ const struct cctl_port &port, std::string &name)
+{
+ if (port.cfiscsi_target.empty()) {
+ log_debugx("CTL port %u \"%s\" wasn't managed by ctld; ",
+ port.port_id, name.c_str());
+ if (!kports.has_port(name)) {
+ if (!kports.add_port(name, port.port_id)) {
+ log_warnx("kports::add_port failed");
+ return;
+ }
+ }
+ return;
+ }
+ if (port.cfiscsi_state != 1) {
+ log_debugx("CTL port %ju is not active (%d); ignoring",
+ (uintmax_t)port.port_id, port.cfiscsi_state);
+ return;
+ }
+
+ const char *t_name = port.cfiscsi_target.c_str();
+ struct target *targ = conf->find_target(t_name);
+ if (targ == nullptr) {
+ targ = conf->add_target(t_name);
+ if (targ == nullptr) {
+ log_warnx("Failed to add target \"%s\"", t_name);
+ return;
+ }
+ }
+
+ if (port.ctld_portal_group_name.empty())
+ return;
+
+ const char *pg_name = port.ctld_portal_group_name.c_str();
+ struct portal_group *pg = conf->find_portal_group(pg_name);
+ if (pg == nullptr) {
+ pg = conf->add_portal_group(pg_name);
+ if (pg == nullptr) {
+ log_warnx("Failed to add portal-group \"%s\"", pg_name);
+ return;
+ }
+ }
+ pg->set_tag(port.cfiscsi_portal_group_tag);
+ if (!conf->add_port(targ, pg, port.port_id)) {
+ log_warnx("Failed to add port for target \"%s\" and portal-group \"%s\"",
+ t_name, pg_name);
+ }
+}
+
+void
+add_nvmf_port(struct conf *conf, const struct cctl_port &port,
+ std::string &name)
+{
+ if (port.nqn.empty() || port.ctld_transport_group_name.empty()) {
+ log_debugx("CTL port %u \"%s\" wasn't managed by ctld; ",
+ port.port_id, name.c_str());
+ return;
+ }
+
+ const char *nqn = port.nqn.c_str();
+ struct target *targ = conf->find_controller(nqn);
+ if (targ == nullptr) {
+ targ = conf->add_controller(nqn);
+ if (targ == nullptr) {
+ log_warnx("Failed to add controller \"%s\"", nqn);
+ return;
+ }
+ }
+
+ const char *tg_name = port.ctld_transport_group_name.c_str();
+ struct portal_group *pg = conf->find_transport_group(tg_name);
+ if (pg == nullptr) {
+ pg = conf->add_transport_group(tg_name);
+ if (pg == nullptr) {
+ log_warnx("Failed to add transport-group \"%s\"",
+ tg_name);
+ return;
+ }
+ }
+ pg->set_tag(port.portid);
+ if (!conf->add_port(targ, pg, port.port_id)) {
+ log_warnx("Failed to add port for controller \"%s\" and transport-group \"%s\"",
+ nqn, tg_name);
+ }
+}
+
conf_up
conf_new_from_kernel(struct kports &kports)
{
@@ -455,51 +551,13 @@ conf_new_from_kernel(struct kports &kports)
name += "/" + std::to_string(port.vp);
}
- if (port.cfiscsi_target.empty()) {
- log_debugx("CTL port %u \"%s\" wasn't managed by ctld; ",
- port.port_id, name.c_str());
- if (!kports.has_port(name)) {
- if (!kports.add_port(name, port.port_id)) {
- log_warnx("kports::add_port failed");
- continue;
- }
- }
- continue;
- }
- if (port.cfiscsi_state != 1) {
- log_debugx("CTL port %ju is not active (%d); ignoring",
- (uintmax_t)port.port_id, port.cfiscsi_state);
- continue;
- }
-
- const char *t_name = port.cfiscsi_target.c_str();
- struct target *targ = conf->find_target(t_name);
- if (targ == NULL) {
- targ = conf->add_target(t_name);
- if (targ == NULL) {
- log_warnx("Failed to add target \"%s\"",
- t_name);
- continue;
- }
- }
-
- if (port.ctld_portal_group_name.empty())
- continue;
- const char *pg_name = port.ctld_portal_group_name.c_str();
- struct portal_group *pg = conf->find_portal_group(pg_name);
- if (pg == NULL) {
- pg = conf->add_portal_group(pg_name);
- if (pg == NULL) {
- log_warnx("Failed to add portal_group \"%s\"",
- pg_name);
- continue;
- }
- }
- pg->set_tag(port.cfiscsi_portal_group_tag);
- if (!conf->add_port(targ, pg, port.port_id)) {
- log_warnx("Failed to add port for target \"%s\" and portal-group \"%s\"",
- t_name, pg_name);
- continue;
+ if (port.port_frontend == "iscsi") {
+ add_iscsi_port(kports, conf.get(), port, name);
+ } else if (port.port_frontend == "nvmf") {
+ add_nvmf_port(conf.get(), port, name);
+ } else {
+ /* XXX: Treat all unknown ports as iSCSI? */
+ add_iscsi_port(kports, conf.get(), port, name);
}
}
@@ -705,65 +763,7 @@ lun::kernel_remove() const
return (true);
}
-void
-ctld_connection::kernel_handoff()
-{
- struct portal_group *pg = conn_portal->portal_group();
- struct ctl_iscsi req;
-
- bzero(&req, sizeof(req));
-
- req.type = CTL_ISCSI_HANDOFF;
- strlcpy(req.data.handoff.initiator_name, conn_initiator_name.c_str(),
- sizeof(req.data.handoff.initiator_name));
- strlcpy(req.data.handoff.initiator_addr, conn_initiator_addr.c_str(),
- sizeof(req.data.handoff.initiator_addr));
- if (!conn_initiator_alias.empty()) {
- strlcpy(req.data.handoff.initiator_alias,
- conn_initiator_alias.c_str(),
- sizeof(req.data.handoff.initiator_alias));
- }
- memcpy(req.data.handoff.initiator_isid, conn_initiator_isid,
- sizeof(req.data.handoff.initiator_isid));
- strlcpy(req.data.handoff.target_name, conn_target->name(),
- sizeof(req.data.handoff.target_name));
- strlcpy(req.data.handoff.offload, pg->offload(),
- sizeof(req.data.handoff.offload));
-#ifdef ICL_KERNEL_PROXY
- if (proxy_mode)
- req.data.handoff.connection_id = conn.conn_socket;
- else
- req.data.handoff.socket = conn.conn_socket;
-#else
- req.data.handoff.socket = conn.conn_socket;
-#endif
- req.data.handoff.portal_group_tag = pg->tag();
- if (conn.conn_header_digest == CONN_DIGEST_CRC32C)
- req.data.handoff.header_digest = CTL_ISCSI_DIGEST_CRC32C;
- if (conn.conn_data_digest == CONN_DIGEST_CRC32C)
- req.data.handoff.data_digest = CTL_ISCSI_DIGEST_CRC32C;
- req.data.handoff.cmdsn = conn.conn_cmdsn;
- req.data.handoff.statsn = conn.conn_statsn;
- req.data.handoff.max_recv_data_segment_length =
- conn.conn_max_recv_data_segment_length;
- req.data.handoff.max_send_data_segment_length =
- conn.conn_max_send_data_segment_length;
- req.data.handoff.max_burst_length = conn.conn_max_burst_length;
- req.data.handoff.first_burst_length = conn.conn_first_burst_length;
- req.data.handoff.immediate_data = conn.conn_immediate_data;
-
- if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) {
- log_err(1, "error issuing CTL_ISCSI ioctl; "
- "dropping connection");
- }
-
- if (req.status != CTL_ISCSI_OK) {
- log_errx(1, "error returned from CTL iSCSI handoff request: "
- "%s; dropping connection", req.error_str);
- }
-}
-
-static bool
+bool
ctl_create_port(const char *driver, const nvlist_t *nvl, uint32_t *ctl_port)
{
struct ctl_req req;
@@ -812,26 +812,6 @@ ctl_create_port(const char *driver, const nvlist_t *nvl, uint32_t *ctl_port)
}
bool
-portal_group_port::kernel_create_port()
-{
- struct portal_group *pg = p_portal_group;
- struct target *targ = p_target;
-
- freebsd::nvlist_up nvl = pg->options();
- nvlist_add_string(nvl.get(), "cfiscsi_target", targ->name());
- nvlist_add_string(nvl.get(), "ctld_portal_group_name", pg->name());
- nvlist_add_stringf(nvl.get(), "cfiscsi_portal_group_tag", "%u",
- pg->tag());
-
- if (targ->has_alias()) {
- nvlist_add_string(nvl.get(), "cfiscsi_target_alias",
- targ->alias());
- }
-
- return (ctl_create_port("iscsi", nvl.get(), &p_ctl_port));
-}
-
-bool
ioctl_port::kernel_create_port()
{
freebsd::nvlist_up nvl(nvlist_create(0));
@@ -971,17 +951,6 @@ ctl_remove_port(const char *driver, nvlist_t *nvl)
}
bool
-portal_group_port::kernel_remove_port()
-{
- freebsd::nvlist_up nvl(nvlist_create(0));
- nvlist_add_string(nvl.get(), "cfiscsi_target", p_target->name());
- nvlist_add_stringf(nvl.get(), "cfiscsi_portal_group_tag", "%u",
- p_portal_group->tag());
-
- return (ctl_remove_port("iscsi", nvl.get()));
-}
-
-bool
ioctl_port::kernel_remove_port()
{
freebsd::nvlist_up nvl(nvlist_create(0));
@@ -1141,7 +1110,7 @@ void
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/login.cc b/usr.sbin/ctld/login.cc
index aa62cd4c8b5b..cda11cc1f21b 100644
--- a/usr.sbin/ctld/login.cc
+++ b/usr.sbin/ctld/login.cc
@@ -42,6 +42,7 @@
#include <cam/ctl/ctl_ioctl.h>
#include "ctld.hh"
+#include "iscsi.hh"
#include "iscsi_proto.h"
#define MAX_DATA_SEGMENT_LENGTH (128 * 1024)
@@ -455,7 +456,7 @@ login_send_chap_success(struct pdu *request,
}
void
-ctld_connection::login_chap(struct auth_group *ag)
+iscsi_connection::login_chap(struct auth_group *ag)
{
std::string user;
const struct auth *auth;
@@ -503,7 +504,7 @@ ctld_connection::login_chap(struct auth_group *ag)
}
void
-ctld_connection::login_negotiate_key(struct pdu *request, const char *name,
+iscsi_connection::login_negotiate_key(struct pdu *request, const char *name,
const char *value, bool skipped_security, struct keys *response_keys)
{
int which;
@@ -694,7 +695,7 @@ login_redirect(struct pdu *request, const char *target_address)
}
bool
-ctld_connection::login_portal_redirect(struct pdu *request)
+iscsi_connection::login_portal_redirect(struct pdu *request)
{
const struct portal_group *pg;
@@ -710,7 +711,7 @@ ctld_connection::login_portal_redirect(struct pdu *request)
}
bool
-ctld_connection::login_target_redirect(struct pdu *request)
+iscsi_connection::login_target_redirect(struct pdu *request)
{
const char *target_address;
@@ -731,7 +732,7 @@ ctld_connection::login_target_redirect(struct pdu *request)
}
void
-ctld_connection::login_negotiate(struct pdu *request)
+iscsi_connection::login_negotiate(struct pdu *request)
{
struct portal_group *pg = conn_portal->portal_group();
struct pdu *response;
@@ -751,7 +752,7 @@ ctld_connection::login_negotiate(struct pdu *request)
conn_max_burst_limit = (1 << 24) - 1;
conn_first_burst_limit = (1 << 24) - 1;
kernel_limits(pg->offload(),
- conn.conn_socket,
+ conn_fd,
&conn_max_recv_data_segment_limit,
&conn_max_send_data_segment_limit,
&conn_max_burst_limit,
@@ -860,7 +861,7 @@ ctld_connection::login_negotiate(struct pdu *request)
}
void
-ctld_connection::login_wait_transition()
+iscsi_connection::login_wait_transition()
{
struct pdu *request, *response;
struct iscsi_bhs_login_request *bhslr;
@@ -884,7 +885,7 @@ ctld_connection::login_wait_transition()
}
void
-ctld_connection::login()
+iscsi_connection::login()
{
struct pdu *request, *response;
struct iscsi_bhs_login_request *bhslr;
diff --git a/usr.sbin/ctld/nvmf.cc b/usr.sbin/ctld/nvmf.cc
new file mode 100644
index 000000000000..d1240bfa4f6c
--- /dev/null
+++ b/usr.sbin/ctld/nvmf.cc
@@ -0,0 +1,478 @@
+/*-
+ * 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 <memory>
+
+#include "ctld.hh"
+#include "nvmf.hh"
+
+#define DEFAULT_MAXH2CDATA (256 * 1024)
+
+struct nvmf_io_portal final : public nvmf_portal {
+ nvmf_io_portal(struct portal_group *pg, const char *listen,
+ portal_protocol protocol, freebsd::addrinfo_up ai,
+ const struct nvmf_association_params &aparams,
+ nvmf_association_up na) :
+ nvmf_portal(pg, listen, protocol, std::move(ai), aparams,
+ std::move(na)) {}
+
+ void handle_connection(freebsd::fd_up fd, const char *host,
+ const struct sockaddr *client_sa) override;
+};
+
+struct nvmf_transport_group final : public portal_group {
+ nvmf_transport_group(struct conf *conf, std::string_view name) :
+ portal_group(conf, name) {}
+
+ const char *keyword() const override
+ { return "transport-group"; }
+
+ void allocate_tag() override;
+ bool add_portal(const char *value, portal_protocol protocol)
+ override;
+ void add_default_portals() override;
+ bool set_filter(const char *str) override;
+
+ virtual port_up create_port(struct target *target, auth_group_sp ag)
+ override;
+ virtual port_up create_port(struct target *target, uint32_t ctl_port)
+ override;
+
+private:
+ struct nvmf_association_params init_aparams(portal_protocol protocol);
+
+ static uint16_t last_port_id;
+};
+
+struct nvmf_port final : public portal_group_port {
+ nvmf_port(struct target *target, struct portal_group *pg,
+ auth_group_sp ag) :
+ portal_group_port(target, pg, ag) {}
+ nvmf_port(struct target *target, struct portal_group *pg,
+ uint32_t ctl_port) :
+ portal_group_port(target, pg, ctl_port) {}
+
+ bool kernel_create_port() override;
+ bool kernel_remove_port() override;
+
+private:
+ static bool modules_loaded;
+ static void load_kernel_modules();
+};
+
+struct nvmf_controller final : public target {
+ nvmf_controller(struct conf *conf, std::string_view name) :
+ target(conf, "controller", name) {}
+
+ bool add_host_nqn(std::string_view name) override;
+ bool add_host_address(const char *addr) override;
+ bool add_namespace(u_int id, const char *lun_name) override;
+ bool add_portal_group(const char *pg_name, const char *ag_name)
+ override;
+ struct lun *start_namespace(u_int id) override;
+
+protected:
+ struct portal_group *default_portal_group() override;
+};
+
+uint16_t nvmf_transport_group::last_port_id = 0;
+bool nvmf_port::modules_loaded = false;
+
+static bool need_tcp_transport = false;
+
+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;
+ int64_t val;
+
+ if (!nvlist_exists_string(nvl, key))
+ return def;
+
+ value = nvlist_get_string(nvl, key);
+ if (expand_number(value, &val) == 0 && val >= 0 &&
+ (uint64_t)val >= minv && (uint64_t)val <= maxv)
+ return (uint64_t)val;
+
+ log_warnx("Invalid value \"%s\" for numeric option %s", value, key);
+ return def;
+}
+
+struct nvmf_association_params
+nvmf_transport_group::init_aparams(portal_protocol protocol)
+{
+ struct nvmf_association_params params;
+ memset(&params, 0, sizeof(params));
+
+ /* Options shared between discovery and I/O associations. */
+ const nvlist_t *nvl = pg_options.get();
+ params.tcp.header_digests = parse_bool(nvl, "HDGST", false);
+ params.tcp.data_digests = parse_bool(nvl, "DDGST", false);
+ uint64_t value = parse_number(nvl, "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;
+
+ switch (protocol) {
+ case portal_protocol::NVME_TCP:
+ params.sq_flow_control = parse_bool(nvl, "SQFC", false);
+ params.dynamic_controller_model = true;
+ params.max_admin_qsize = parse_number(nvl, "max_admin_qsize",
+ NVME_MAX_ADMIN_ENTRIES, NVME_MIN_ADMIN_ENTRIES,
+ NVME_MAX_ADMIN_ENTRIES);
+ params.max_io_qsize = parse_number(nvl, "max_io_qsize",
+ NVME_MAX_IO_ENTRIES, NVME_MIN_IO_ENTRIES,
+ NVME_MAX_IO_ENTRIES);
+ params.tcp.pda = 0;
+ break;
+ case portal_protocol::NVME_DISCOVERY_TCP:
+ params.sq_flow_control = false;
+ params.dynamic_controller_model = true;
+ params.max_admin_qsize = NVME_MAX_ADMIN_ENTRIES;
+ params.tcp.pda = 0;
+ break;
+ default:
+ __assert_unreachable();
+ }
+
+ return params;
+}
+
+portal_group_up
+nvmf_make_transport_group(struct conf *conf, std::string_view name)
+{
+ return std::make_unique<nvmf_transport_group>(conf, name);
+}
+
+target_up
+nvmf_make_controller(struct conf *conf, std::string_view name)
+{
+ return std::make_unique<nvmf_controller>(conf, name);
+}
+
+void
+nvmf_transport_group::allocate_tag()
+{
+ set_tag(++last_port_id);
+}
+
+bool
+nvmf_transport_group::add_portal(const char *value, portal_protocol protocol)
+{
+ freebsd::addrinfo_up ai;
+ enum nvmf_trtype trtype;
+
+ switch (protocol) {
+ case portal_protocol::NVME_TCP:
+ trtype = NVMF_TRTYPE_TCP;
+ ai = parse_addr_port(value, "4420");
+ break;
+ case portal_protocol::NVME_DISCOVERY_TCP:
+ trtype = NVMF_TRTYPE_TCP;
+ ai = parse_addr_port(value, "8009");
+ break;
+ default:
+ log_warnx("unsupported transport protocol for %s", value);
+ return false;
+ }
+
+ if (!ai) {
+ log_warnx("invalid listen address %s", value);
+ return false;
+ }
+
+ struct nvmf_association_params aparams = init_aparams(protocol);
+ nvmf_association_up association(nvmf_allocate_association(trtype, true,
+ &aparams));
+ if (!association) {
+ log_warn("Failed to create NVMe controller association");
+ return false;
+ }
+
+ /*
+ * XXX: getaddrinfo(3) may return multiple addresses; we should turn
+ * those into multiple portals.
+ */
+
+ portal_up portal;
+ if (protocol == portal_protocol::NVME_DISCOVERY_TCP) {
+ portal = std::make_unique<nvmf_discovery_portal>(this, value,
+ protocol, std::move(ai), aparams, std::move(association));
+ } else {
+ portal = std::make_unique<nvmf_io_portal>(this, value,
+ protocol, std::move(ai), aparams, std::move(association));
+ need_tcp_transport = true;
+ }
+
+ pg_portals.emplace_back(std::move(portal));
+ return true;
+}
+
+void
+nvmf_transport_group::add_default_portals()
+{
+ add_portal("0.0.0.0", portal_protocol::NVME_DISCOVERY_TCP);
+ add_portal("[::]", portal_protocol::NVME_DISCOVERY_TCP);
+ add_portal("0.0.0.0", portal_protocol::NVME_TCP);
+ add_portal("[::]", portal_protocol::NVME_TCP);
+}
+
+bool
+nvmf_transport_group::set_filter(const char *str)
+{
+ enum discovery_filter filter;
+
+ if (strcmp(str, "none") == 0) {
+ filter = discovery_filter::NONE;
+ } else if (strcmp(str, "address") == 0) {
+ filter = discovery_filter::PORTAL;
+ } else if (strcmp(str, "address-name") == 0) {
+ filter = discovery_filter::PORTAL_NAME;
+ } else {
+ log_warnx("invalid discovery-filter \"%s\" for transport-group "
+ "\"%s\"; valid values are \"none\", \"address\", "
+ "and \"address-name\"",
+ str, name());
+ return false;
+ }
+
+ if (pg_discovery_filter != discovery_filter::UNKNOWN &&
+ pg_discovery_filter != filter) {
+ log_warnx("cannot set discovery-filter to \"%s\" for "
+ "transport-group \"%s\"; already has a different "
+ "value", str, name());
+ return false;
+ }
+
+ pg_discovery_filter = filter;
+ return true;
+}
+
+port_up
+nvmf_transport_group::create_port(struct target *target, auth_group_sp ag)
+{
+ return std::make_unique<nvmf_port>(target, this, ag);
+}
+
+port_up
+nvmf_transport_group::create_port(struct target *target, uint32_t ctl_port)
+{
+ return std::make_unique<nvmf_port>(target, this, ctl_port);
+}
+
+void
+nvmf_port::load_kernel_modules()
+{
+ int saved_errno;
+
+ if (modules_loaded)
+ return;
+
+ saved_errno = errno;
+ if (modfind("nvmft") == -1 && kldload("nvmft") == -1)
+ log_warn("couldn't load nvmft");
+
+ if (need_tcp_transport) {
+ if (modfind("nvmf/tcp") == -1 && kldload("nvmf_tcp") == -1)
+ log_warn("couldn't load nvmf_tcp");
+ }
+
+ errno = saved_errno;
+ modules_loaded = true;
+}
+
+bool
+nvmf_port::kernel_create_port()
+{
+ struct portal_group *pg = p_portal_group;
+ struct target *targ = p_target;
+
+ load_kernel_modules();
+
+ freebsd::nvlist_up nvl = pg->options();
+ nvlist_add_string(nvl.get(), "subnqn", targ->name());
+ nvlist_add_string(nvl.get(), "ctld_transport_group_name",
+ pg->name());
+ nvlist_add_stringf(nvl.get(), "portid", "%u", pg->tag());
+ if (!nvlist_exists_string(nvl.get(), "max_io_qsize"))
+ nvlist_add_stringf(nvl.get(), "max_io_qsize", "%u",
+ NVME_MAX_IO_ENTRIES);
+
+ return ctl_create_port("nvmf", nvl.get(), &p_ctl_port);
+}
+
+bool
+nvmf_port::kernel_remove_port()
+{
+ freebsd::nvlist_up nvl(nvlist_create(0));
+ nvlist_add_string(nvl.get(), "subnqn", p_target->name());
+
+ return ctl_remove_port("nvmf", nvl.get());
+}
+
+bool
+nvmf_controller::add_host_nqn(std::string_view name)
+{
+ if (!use_private_auth("host-nqn"))
+ return false;
+ return t_auth_group->add_host_nqn(name);
+}
+
+bool
+nvmf_controller::add_host_address(const char *addr)
+{
+ if (!use_private_auth("host-address"))
+ return false;
+ return t_auth_group->add_host_address(addr);
+}
+
+bool
+nvmf_controller::add_namespace(u_int id, const char *lun_name)
+{
+ if (id == 0) {
+ log_warnx("namespace ID cannot be 0 for %s", label());
+ return false;
+ }
+
+ std::string lun_label = "namespace ID " + std::to_string(id - 1);
+ return target::add_lun(id, lun_label.c_str(), lun_name);
+}
+
+bool
+nvmf_controller::add_portal_group(const char *pg_name, const char *ag_name)
+{
+ struct portal_group *pg;
+ auth_group_sp ag;
+
+ pg = t_conf->find_transport_group(pg_name);
+ if (pg == NULL) {
+ log_warnx("unknown transport-group \"%s\" for %s", pg_name,
+ label());
+ return false;
+ }
+
+ if (ag_name != NULL) {
+ ag = t_conf->find_auth_group(ag_name);
+ if (ag == NULL) {
+ log_warnx("unknown auth-group \"%s\" for %s", ag_name,
+ label());
+ return false;
+ }
+ }
+
+ if (!t_conf->add_port(this, pg, std::move(ag))) {
+ log_warnx("can't link transport-group \"%s\" to %s", pg_name,
+ label());
+ return false;
+ }
+ return true;
+}
+
+struct lun *
+nvmf_controller::start_namespace(u_int id)
+{
+ if (id == 0) {
+ log_warnx("namespace ID cannot be 0 for %s", label());
+ return nullptr;
+ }
+
+ std::string lun_label = "namespace ID " + std::to_string(id - 1);
+ std::string lun_name = freebsd::stringf("%s,nsid,%u", name(), id);
+ return target::start_lun(id, lun_label.c_str(), lun_name.c_str());
+}
+
+struct portal_group *
+nvmf_controller::default_portal_group()
+{
+ return t_conf->find_transport_group("default");
+}
+
+void
+nvmf_io_portal::handle_connection(freebsd::fd_up fd, const char *host __unused,
+ const struct sockaddr *client_sa __unused)
+{
+ struct nvmf_qpair_params qparams;
+ memset(&qparams, 0, sizeof(qparams));
+ qparams.tcp.fd = fd;
+
+ struct nvmf_capsule *nc = NULL;
+ struct nvmf_fabric_connect_data data;
+ nvmf_qpair_up qp(nvmf_accept(association(), &qparams, &nc, &data));
+ if (!qp) {
+ log_warnx("Failed to create NVMe I/O qpair: %s",
+ nvmf_association_error(association()));
+ return;
+ }
+ nvmf_capsule_up nc_guard(nc);
+ const struct nvmf_fabric_connect_cmd *cmd =
+ (const struct nvmf_fabric_connect_cmd *)nvmf_capsule_sqe(nc);
+
+ struct ctl_nvmf req;
+ memset(&req, 0, sizeof(req));
+ req.type = CTL_NVMF_HANDOFF;
+ int error = nvmf_handoff_controller_qpair(qp.get(), cmd, &data,
+ &req.data.handoff);
+ if (error != 0) {
+ log_warnc(error,
+ "Failed to prepare NVMe I/O qpair for handoff");
+ return;
+ }
+
+ 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);
+}
diff --git a/usr.sbin/ctld/nvmf.hh b/usr.sbin/ctld/nvmf.hh
new file mode 100644
index 000000000000..0b4f8d45adfd
--- /dev/null
+++ b/usr.sbin/ctld/nvmf.hh
@@ -0,0 +1,71 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Chelsio Communications, Inc.
+ * Written by: John Baldwin <jhb@FreeBSD.org>
+ */
+
+#ifndef __NVMF_HH__
+#define __NVMF_HH__
+
+struct nvmf_association_deleter {
+ void operator()(struct nvmf_association *na) const
+ {
+ nvmf_free_association(na);
+ }
+};
+
+using nvmf_association_up = std::unique_ptr<nvmf_association,
+ nvmf_association_deleter>;
+
+struct nvmf_capsule_deleter {
+ void operator()(struct nvmf_capsule *nc) const
+ {
+ nvmf_free_capsule(nc);
+ }
+};
+
+using nvmf_capsule_up = std::unique_ptr<nvmf_capsule, nvmf_capsule_deleter>;
+
+struct nvmf_qpair_deleter {
+ void operator()(struct nvmf_qpair *qp) const
+ {
+ nvmf_free_qpair(qp);
+ }
+};
+
+using nvmf_qpair_up = std::unique_ptr<nvmf_qpair, nvmf_qpair_deleter>;
+
+struct nvmf_portal : public portal {
+ nvmf_portal(struct portal_group *pg, const char *listen,
+ portal_protocol protocol, freebsd::addrinfo_up ai,
+ const struct nvmf_association_params &aparams,
+ nvmf_association_up na) :
+ portal(pg, listen, protocol, std::move(ai)),
+ p_aparams(aparams), p_association(std::move(na)) {}
+ virtual ~nvmf_portal() override = default;
+
+ const struct nvmf_association_params *aparams() const
+ { return &p_aparams; }
+
+protected:
+ struct nvmf_association *association() { return p_association.get(); }
+
+private:
+ struct nvmf_association_params p_aparams;
+ nvmf_association_up p_association;
+};
+
+struct nvmf_discovery_portal final : public nvmf_portal {
+ nvmf_discovery_portal(struct portal_group *pg, const char *listen,
+ portal_protocol protocol, freebsd::addrinfo_up ai,
+ const struct nvmf_association_params &aparams,
+ nvmf_association_up na) :
+ nvmf_portal(pg, listen, protocol, std::move(ai), aparams,
+ std::move(na)) {}
+
+ void handle_connection(freebsd::fd_up fd, const char *host,
+ const struct sockaddr *client_sa) override;
+};
+
+#endif /* !__NVMF_HH__ */
diff --git a/usr.sbin/ctld/nvmf_discovery.cc b/usr.sbin/ctld/nvmf_discovery.cc
new file mode 100644
index 000000000000..839b69fdac14
--- /dev/null
+++ b/usr.sbin/ctld/nvmf_discovery.cc
@@ -0,0 +1,518 @@
+/*-
+ * 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.hh"
+#include "nvmf.hh"
+
+struct discovery_log {
+ discovery_log(const struct portal_group *pg);
+
+ const char *data() const { return buf.data(); }
+ size_t length() const { return buf.size(); }
+
+ void append(const struct nvme_discovery_log_entry *entry);
+
+private:
+ struct nvme_discovery_log *header()
+ { return reinterpret_cast<struct nvme_discovery_log *>(buf.data()); }
+
+ std::vector<char> buf;
+};
+
+struct discovery_controller {
+ discovery_controller(freebsd::fd_up s, struct nvmf_qpair *qp,
+ const discovery_log &discovery_log);
+
+ void handle_admin_commands();
+private:
+ bool update_cc(uint32_t new_cc);
+ void handle_property_get(const struct nvmf_capsule *nc,
+ const struct nvmf_fabric_prop_get_cmd *pget);
+ void handle_property_set(const struct nvmf_capsule *nc,
+ const struct nvmf_fabric_prop_set_cmd *pset);
+ void handle_fabrics_command(const struct nvmf_capsule *nc,
+ const struct nvmf_fabric_cmd *cmd);
+ void handle_identify_command(const struct nvmf_capsule *nc,
+ const struct nvme_command *cmd);
+ void handle_get_log_page_command(const struct nvmf_capsule *nc,
+ const struct nvme_command *cmd);
+
+ struct nvmf_qpair *qp;
+
+ uint64_t cap = 0;
+ uint32_t vs = 0;
+ uint32_t cc = 0;
+ uint32_t csts = 0;
+
+ bool shutdown = false;
+
+ struct nvme_controller_data cdata;
+
+ const struct discovery_log &discovery_log;
+ freebsd::fd_up s;
+};
+
+discovery_log::discovery_log(const struct portal_group *pg) :
+ buf(sizeof(nvme_discovery_log))
+{
+ struct nvme_discovery_log *log = header();
+
+ log->genctr = htole32(pg->conf()->genctr());
+ log->recfmt = 0;
+}
+
+void
+discovery_log::append(const struct nvme_discovery_log_entry *entry)
+{
+ const char *cp = reinterpret_cast<const char *>(entry);
+ buf.insert(buf.end(), cp, cp + sizeof(*entry));
+
+ struct nvme_discovery_log *log = header();
+ log->numrec = htole32(le32toh(log->numrec) + 1);
+}
+
+static bool
+discovery_controller_filtered(const struct portal_group *pg,
+ const struct sockaddr *client_sa, std::string_view hostnqn,
+ const struct port *port)
+{
+ const struct target *targ = port->target();
+ const struct auth_group *ag = port->auth_group();
+ if (ag == nullptr)
+ ag = targ->auth_group();
+
+ assert(pg->discovery_filter() != discovery_filter::UNKNOWN);
+
+ if (pg->discovery_filter() >= discovery_filter::PORTAL &&
+ !ag->host_permitted(client_sa)) {
+ log_debugx("host address does not match addresses "
+ "allowed for controller \"%s\"; skipping", targ->name());
+ return true;
+ }
+
+ if (pg->discovery_filter() >= discovery_filter::PORTAL_NAME &&
+ !ag->host_permitted(hostnqn) != 0) {
+ log_debugx("HostNQN does not match NQNs "
+ "allowed for controller \"%s\"; skipping", targ->name());
+ return true;
+ }
+
+ /* XXX: auth not yet implemented for NVMe */
+
+ return false;
+}
+
+static bool
+portal_uses_wildcard_address(const struct portal *p)
+{
+ const struct addrinfo *ai = p->ai();
+
+ switch (ai->ai_family) {
+ case AF_INET:
+ {
+ const struct sockaddr_in *sin;
+
+ sin = (const struct sockaddr_in *)ai->ai_addr;
+ return sin->sin_addr.s_addr == htonl(INADDR_ANY);
+ }
+ case AF_INET6:
+ {
+ const struct sockaddr_in6 *sin6;
+
+ sin6 = (const 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,
+ const struct target *target, const struct portal *portal,
+ const char *wildcard_host)
+{
+ /*
+ * The TCP port for I/O controllers might not be fixed, so
+ * fetch the sockaddr of the socket to determine which port
+ * the kernel chose.
+ */
+ struct sockaddr_storage ss;
+ socklen_t len = sizeof(ss);
+ if (getsockname(portal->socket(), (struct sockaddr *)&ss, &len) == -1) {
+ log_warn("Failed getsockname building discovery log entry");
+ return false;
+ }
+
+ const struct nvmf_association_params *aparams =
+ static_cast<const nvmf_portal *>(portal)->aparams();
+
+ memset(entry, 0, sizeof(*entry));
+ entry->trtype = NVMF_TRTYPE_TCP;
+ int error = getnameinfo((struct sockaddr *)&ss, len,
+ (char *)entry->traddr, sizeof(entry->traddr),
+ (char *)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((char *)entry->traddr, wildcard_host,
+ sizeof(entry->traddr));
+ switch (portal->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(portal->portal_group()->tag());
+ entry->cntlid = htole16(NVMF_CNTLID_DYNAMIC);
+ entry->aqsz = aparams->max_admin_qsize;
+ strncpy((char *)entry->subnqn, target->name(), sizeof(entry->subnqn));
+ return true;
+}
+
+static discovery_log
+build_discovery_log_page(const struct portal_group *pg, int fd,
+ const struct sockaddr *client_sa,
+ const struct nvmf_fabric_connect_data &data)
+{
+ discovery_log discovery_log(pg);
+
+ struct sockaddr_storage ss;
+ socklen_t len = sizeof(ss);
+ if (getsockname(fd, (struct sockaddr *)&ss, &len) == -1) {
+ log_warn("build_discovery_log_page: getsockname");
+ return discovery_log;
+ }
+
+ char wildcard_host[NI_MAXHOST];
+ int 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 discovery_log;
+ }
+
+ const char *nqn = (const char *)data.hostnqn;
+ std::string hostnqn(nqn, strnlen(nqn, sizeof(data.hostnqn)));
+ for (const auto &kv : pg->ports()) {
+ const struct port *port = kv.second;
+ if (discovery_controller_filtered(pg, client_sa, hostnqn, port))
+ continue;
+
+ for (const portal_up &portal : pg->portals()) {
+ if (portal->protocol() != portal_protocol::NVME_TCP)
+ continue;
+
+ if (portal_uses_wildcard_address(portal.get()) &&
+ portal->ai()->ai_family != client_sa->sa_family)
+ continue;
+
+ struct nvme_discovery_log_entry entry;
+ if (init_discovery_log_entry(&entry, port->target(),
+ portal.get(), wildcard_host))
+ discovery_log.append(&entry);
+ }
+ }
+
+ return discovery_log;
+}
+
+bool
+discovery_controller::update_cc(uint32_t new_cc)
+{
+ uint32_t changes;
+
+ if (shutdown)
+ return false;
+ if (!nvmf_validate_cc(qp, cap, cc, new_cc))
+ return false;
+
+ changes = cc ^ new_cc;
+ cc = new_cc;
+
+ /* Handle shutdown requests. */
+ if (NVMEV(NVME_CC_REG_SHN, changes) != 0 &&
+ NVMEV(NVME_CC_REG_SHN, new_cc) != 0) {
+ csts &= ~NVMEM(NVME_CSTS_REG_SHST);
+ csts |= NVMEF(NVME_CSTS_REG_SHST, NVME_SHST_COMPLETE);
+ shutdown = true;
+ }
+
+ if (NVMEV(NVME_CC_REG_EN, changes) != 0) {
+ if (NVMEV(NVME_CC_REG_EN, new_cc) == 0) {
+ /* Controller reset. */
+ csts = 0;
+ shutdown = true;
+ } else
+ csts |= NVMEF(NVME_CSTS_REG_RDY, 1);
+ }
+ return true;
+}
+
+void
+discovery_controller::handle_property_get(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(cap);
+ break;
+ case NVMF_PROP_VS:
+ if (pget->attrib.size != NVMF_PROP_SIZE_4)
+ goto error;
+ rsp.value.u32.low = htole32(vs);
+ break;
+ case NVMF_PROP_CC:
+ if (pget->attrib.size != NVMF_PROP_SIZE_4)
+ goto error;
+ rsp.value.u32.low = htole32(cc);
+ break;
+ case NVMF_PROP_CSTS:
+ if (pget->attrib.size != NVMF_PROP_SIZE_4)
+ goto error;
+ rsp.value.u32.low = htole32(csts);
+ break;
+ default:
+ goto error;
+ }
+
+ nvmf_send_response(nc, &rsp);
+ return;
+error:
+ nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD);
+}
+
+void
+discovery_controller::handle_property_set(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(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);
+}
+
+void
+discovery_controller::handle_fabrics_command(const struct nvmf_capsule *nc,
+ const struct nvmf_fabric_cmd *fc)
+{
+ switch (fc->fctype) {
+ case NVMF_FABRIC_COMMAND_PROPERTY_GET:
+ handle_property_get(nc,
+ (const struct nvmf_fabric_prop_get_cmd *)fc);
+ break;
+ case NVMF_FABRIC_COMMAND_PROPERTY_SET:
+ handle_property_set(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;
+ }
+}
+
+void
+discovery_controller::handle_identify_command(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, &cdata, sizeof(cdata));
+ return;
+error:
+ nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD);
+}
+
+void
+discovery_controller::handle_get_log_page_command(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;
+ }
+
+ offset = nvmf_get_log_page_offset(cmd);
+ if (offset >= discovery_log.length())
+ goto error;
+
+ length = nvmf_get_log_page_length(cmd);
+ if (length > discovery_log.length() - offset)
+ length = discovery_log.length() - offset;
+
+ nvmf_send_controller_data(nc, discovery_log.data() + offset, length);
+ return;
+error:
+ nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD);
+}
+
+void
+discovery_controller::handle_admin_commands()
+{
+ for (;;) {
+ struct nvmf_capsule *nc;
+ int error = nvmf_controller_receive_capsule(qp, &nc);
+ if (error != 0) {
+ if (error != ECONNRESET)
+ log_warnc(error,
+ "Failed to read command capsule");
+ break;
+ }
+ nvmf_capsule_up nc_guard(nc);
+
+ const struct nvme_command *cmd =
+ (const struct nvme_command *)nvmf_capsule_sqe(nc);
+
+ /*
+ * Only permit Fabrics commands while a controller is
+ * disabled.
+ */
+ if (NVMEV(NVME_CC_REG_EN, 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);
+ continue;
+ }
+
+ switch (cmd->opc) {
+ case NVME_OPC_FABRICS_COMMANDS:
+ handle_fabrics_command(nc,
+ (const struct nvmf_fabric_cmd *)cmd);
+ break;
+ case NVME_OPC_IDENTIFY:
+ handle_identify_command(nc, cmd);
+ break;
+ case NVME_OPC_GET_LOG_PAGE:
+ handle_get_log_page_command(nc, cmd);
+ break;
+ default:
+ log_warnx("Unsupported admin opcode %#x", cmd->opc);
+ nvmf_send_generic_error(nc, NVME_SC_INVALID_OPCODE);
+ break;
+ }
+ }
+}
+
+discovery_controller::discovery_controller(freebsd::fd_up fd,
+ struct nvmf_qpair *qp, const struct discovery_log &discovery_log) :
+ qp(qp), discovery_log(discovery_log), s(std::move(fd))
+{
+ nvmf_init_discovery_controller_data(qp, &cdata);
+ cap = nvmf_controller_cap(qp);
+ vs = cdata.ver;
+}
+
+void
+nvmf_discovery_portal::handle_connection(freebsd::fd_up fd,
+ const char *host __unused, const struct sockaddr *client_sa)
+{
+ struct nvmf_qpair_params qparams;
+ memset(&qparams, 0, sizeof(qparams));
+ qparams.tcp.fd = fd;
+
+ struct nvmf_capsule *nc = NULL;
+ struct nvmf_fabric_connect_data data;
+ nvmf_qpair_up qp(nvmf_accept(association(), &qparams, &nc, &data));
+ if (!qp) {
+ log_warnx("Failed to create NVMe discovery qpair: %s",
+ nvmf_association_error(association()));
+ return;
+ }
+ nvmf_capsule_up nc_guard(nc);
+
+ if (strncmp((char *)data.subnqn, NVMF_DISCOVERY_NQN,
+ sizeof(data.subnqn)) != 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));
+ return;
+ }
+
+ /* Just use a controller ID of 1 for all discovery controllers. */
+ int error = nvmf_finish_accept(nc, 1);
+ if (error != 0) {
+ log_warnc(error, "Failed to send NVMe CONNECT reponse");
+ return;
+ }
+ nc_guard.reset();
+
+ discovery_log discovery_log = build_discovery_log_page(portal_group(),
+ fd, client_sa, data);
+
+ discovery_controller controller(std::move(fd), qp.get(), discovery_log);
+ controller.handle_admin_commands();
+}
diff --git a/usr.sbin/ctld/parse.y b/usr.sbin/ctld/parse.y
index 432183ed794c..5725c16b459a 100644
--- a/usr.sbin/ctld/parse.y
+++ b/usr.sbin/ctld/parse.y
@@ -54,12 +54,14 @@ extern void yyrestart(FILE *);
%}
%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
@@ -98,9 +100,13 @@ statement:
|
portal_group
|
+ transport_group
+ |
lun
|
target
+ |
+ controller
;
debug: DEBUG STR
@@ -232,6 +238,10 @@ auth_group_entry:
|
auth_group_chap_mutual
|
+ auth_group_host_address
+ |
+ auth_group_host_nqn
+ |
auth_group_initiator_name
|
auth_group_initiator_portal
@@ -274,6 +284,28 @@ auth_group_chap_mutual: CHAP_MUTUAL STR STR STR STR
}
;
+auth_group_host_address: HOST_ADDRESS STR
+ {
+ bool ok;
+
+ ok = auth_group_add_host_address($2);
+ free($2);
+ if (!ok)
+ return (1);
+ }
+ ;
+
+auth_group_host_nqn: HOST_NQN STR
+ {
+ bool ok;
+
+ ok = auth_group_add_host_nqn($2);
+ free($2);
+ if (!ok)
+ return (1);
+ }
+ ;
+
auth_group_initiator_name: INITIATOR_NAME STR
{
bool ok;
@@ -502,6 +534,71 @@ portal_group_pcp: PCP STR
}
;
+transport_group: TRANSPORT_GROUP transport_group_name
+ OPENING_BRACKET transport_group_entries CLOSING_BRACKET
+ {
+ portal_group_finish();
+ }
+ ;
+
+transport_group_name: STR
+ {
+ bool ok;
+
+ ok = transport_group_start($1);
+ free($1);
+ if (!ok)
+ return (1);
+ }
+ ;
+
+transport_group_entries:
+ |
+ transport_group_entries transport_group_entry
+ |
+ transport_group_entries transport_group_entry SEMICOLON
+ ;
+
+transport_group_entry:
+ portal_group_discovery_auth_group
+ |
+ portal_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_listen_discovery_tcp: LISTEN DISCOVERY_TCP STR
+ {
+ bool ok;
+
+ ok = transport_group_add_listen_discovery_tcp($3);
+ free($3);
+ if (!ok)
+ return (1);
+ }
+ ;
+
+transport_group_listen_tcp: LISTEN TCP STR
+ {
+ bool ok;
+
+ ok = transport_group_add_listen_tcp($3);
+ free($3);
+ if (!ok)
+ return (1);
+ }
+ ;
+
lun: LUN lun_name
OPENING_BRACKET lun_entries CLOSING_BRACKET
{
@@ -738,6 +835,133 @@ target_lun_ref: LUN STR STR
}
;
+controller: CONTROLLER controller_name
+ OPENING_BRACKET controller_entries CLOSING_BRACKET
+ {
+ target_finish();
+ }
+ ;
+
+controller_name: STR
+ {
+ bool ok;
+
+ ok = controller_start($1);
+ free($1);
+ if (!ok)
+ 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
+ {
+ bool ok;
+
+ ok = controller_add_host_address($2);
+ free($2);
+ if (!ok)
+ return (1);
+ }
+ ;
+
+controller_host_nqn: HOST_NQN STR
+ {
+ bool ok;
+
+ ok = controller_add_host_nqn($2);
+ free($2);
+ if (!ok)
+ return (1);
+ }
+ ;
+
+controller_transport_group: TRANSPORT_GROUP STR STR
+ {
+ bool ok;
+
+ ok = target_add_portal_group($2, $3);
+ free($2);
+ free($3);
+ if (!ok)
+ return (1);
+ }
+ | TRANSPORT_GROUP STR
+ {
+ bool ok;
+
+ ok = target_add_portal_group($2, NULL);
+ free($2);
+ if (!ok)
+ return (1);
+ }
+ ;
+
+controller_namespace: NAMESPACE ns_number
+ OPENING_BRACKET lun_entries CLOSING_BRACKET
+ {
+ lun_finish();
+ }
+ ;
+
+ns_number: STR
+ {
+ uint64_t tmp;
+
+ if (expand_number($1, &tmp) != 0) {
+ yyerror("invalid numeric value");
+ free($1);
+ return (1);
+ }
+ free($1);
+
+ if (!controller_start_namespace(tmp))
+ return (1);
+ }
+ ;
+
+controller_namespace_ref: NAMESPACE STR STR
+ {
+ uint64_t tmp;
+ bool ok;
+
+ if (expand_number($2, &tmp) != 0) {
+ yyerror("invalid numeric value");
+ free($2);
+ free($3);
+ return (1);
+ }
+ free($2);
+
+ ok = controller_add_namespace(tmp, $3);
+ free($3);
+ if (!ok)
+ return (1);
+ }
+ ;
+
lun_entries:
|
lun_entries lun_entry
diff --git a/usr.sbin/ctld/token.l b/usr.sbin/ctld/token.l
index c8f54103db55..5f959f648969 100644
--- a/usr.sbin/ctld/token.l
+++ b/usr.sbin/ctld/token.l
@@ -54,21 +54,26 @@ backend { return BACKEND; }
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 @@ serial { return SERIAL; }
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.cc b/usr.sbin/ctld/uclparse.cc
index 843e727a2e52..8a62636ffec6 100644
--- a/usr.sbin/ctld/uclparse.cc
+++ b/usr.sbin/ctld/uclparse.cc
@@ -64,6 +64,10 @@ static bool uclparse_lun(const char *, const ucl::Ucl &);
static bool uclparse_lun_entries(const char *, const ucl::Ucl &);
static bool uclparse_auth_group(const char *, const ucl::Ucl &);
static bool uclparse_portal_group(const char *, const ucl::Ucl &);
+static bool uclparse_transport_group(const char *, const ucl::Ucl &);
+static bool uclparse_controller(const char *, const ucl::Ucl &);
+static bool uclparse_controller_transport_group(const char *, const ucl::Ucl &);
+static bool uclparse_controller_namespace(const char *, const ucl::Ucl &);
static bool uclparse_target(const char *, const ucl::Ucl &);
static bool uclparse_target_portal_group(const char *, const ucl::Ucl &);
static bool uclparse_target_lun(const char *, const ucl::Ucl &);
@@ -230,6 +234,47 @@ uclparse_target_portal_group(const char *t_name, const ucl::Ucl &obj)
}
static bool
+uclparse_controller_transport_group(const char *t_name, const ucl::Ucl &obj)
+{
+ /*
+ * If the value is a single string, assume it is a
+ * transport-group name.
+ */
+ if (obj.type() == UCL_STRING)
+ return target_add_portal_group(obj.string_value().c_str(),
+ nullptr);
+
+ if (obj.type() != UCL_OBJECT) {
+ log_warnx("transport-group section in controller \"%s\" must "
+ "be an object or string", t_name);
+ return false;
+ }
+
+ auto portal_group = obj["name"];
+ if (!portal_group || portal_group.type() != UCL_STRING) {
+ log_warnx("transport-group section in controller \"%s\" is "
+ "missing \"name\" string key", t_name);
+ return false;
+ }
+
+ auto auth_group = obj["auth-group-name"];
+ if (auth_group) {
+ if (auth_group.type() != UCL_STRING) {
+ log_warnx("\"auth-group-name\" property in "
+ "transport-group section for controller \"%s\" is "
+ "not a string", t_name);
+ return false;
+ }
+ return target_add_portal_group(
+ portal_group.string_value().c_str(),
+ auth_group.string_value().c_str());
+ }
+
+ return target_add_portal_group(portal_group.string_value().c_str(),
+ nullptr);
+}
+
+static bool
uclparse_target_lun(const char *t_name, const ucl::Ucl &obj)
{
char *end;
@@ -285,6 +330,62 @@ uclparse_target_lun(const char *t_name, const ucl::Ucl &obj)
}
static bool
+uclparse_controller_namespace(const char *t_name, const ucl::Ucl &obj)
+{
+ char *end;
+ u_int id;
+
+ std::string key = obj.key();
+ if (!key.empty()) {
+ id = strtoul(key.c_str(), &end, 0);
+ if (*end != '\0') {
+ log_warnx("namespace key \"%s\" in controller \"%s\""
+ " is invalid", key.c_str(), t_name);
+ return false;
+ }
+
+ if (obj.type() == UCL_STRING)
+ return controller_add_namespace(id,
+ obj.string_value().c_str());
+ }
+
+ if (obj.type() != UCL_OBJECT) {
+ log_warnx("namespace section entries in controller \"%s\""
+ " must be objects", t_name);
+ return false;
+ }
+
+ if (key.empty()) {
+ auto num = obj["number"];
+ if (!num || num.type() != UCL_INT) {
+ log_warnx("namespace section in controller \"%s\" is "
+ "missing \"id\" integer property", t_name);
+ return (false);
+ }
+ id = num.int_value();
+ }
+
+ auto name = obj["name"];
+ if (!name) {
+ if (!controller_start_namespace(id))
+ return false;
+
+ std::string lun_name =
+ freebsd::stringf("namespace %u for controller \"%s\"", id,
+ t_name);
+ return uclparse_lun_entries(lun_name.c_str(), obj);
+ }
+
+ if (name.type() != UCL_STRING) {
+ log_warnx("\"name\" property for namespace %u for "
+ "controller \"%s\" is not a string", id, t_name);
+ return (false);
+ }
+
+ return controller_add_namespace(id, name.string_value().c_str());
+}
+
+static bool
uclparse_toplevel(const ucl::Ucl &top)
{
/* Pass 1 - everything except targets */
@@ -390,6 +491,19 @@ uclparse_toplevel(const ucl::Ucl &top)
}
}
+ if (key == "transport-group") {
+ if (obj.type() == UCL_OBJECT) {
+ for (const auto &child : obj) {
+ if (!uclparse_transport_group(
+ child.key().c_str(), child))
+ return false;
+ }
+ } else {
+ log_warnx("\"transport-group\" section is not an object");
+ return false;
+ }
+ }
+
if (key == "lun") {
if (obj.type() == UCL_OBJECT) {
for (const auto &child : obj) {
@@ -408,6 +522,19 @@ uclparse_toplevel(const ucl::Ucl &top)
for (const auto &obj : top) {
std::string key = obj.key();
+ if (key == "controller") {
+ if (obj.type() == UCL_OBJECT) {
+ for (const auto &child : obj) {
+ if (!uclparse_controller(
+ child.key().c_str(), child))
+ return false;
+ }
+ } else {
+ log_warnx("\"controller\" section is not an object");
+ return false;
+ }
+ }
+
if (key == "target") {
if (obj.type() == UCL_OBJECT) {
for (const auto &child : obj) {
@@ -474,6 +601,44 @@ uclparse_auth_group(const char *name, const ucl::Ucl &top)
}
}
+ if (key == "host-address") {
+ if (obj.type() == UCL_STRING) {
+ if (!auth_group_add_host_address(
+ obj.string_value().c_str()))
+ return false;
+ } else if (obj.type() == UCL_ARRAY) {
+ for (const auto &tmp : obj) {
+ if (!auth_group_add_host_address(
+ tmp.string_value().c_str()))
+ return false;
+ }
+ } else {
+ log_warnx("\"host-address\" property of "
+ "auth-group \"%s\" is not an array or string",
+ name);
+ return false;
+ }
+ }
+
+ if (key == "host-nqn") {
+ if (obj.type() == UCL_STRING) {
+ if (!auth_group_add_host_nqn(
+ obj.string_value().c_str()))
+ return false;
+ } else if (obj.type() == UCL_ARRAY) {
+ for (const auto &tmp : obj) {
+ if (!auth_group_add_host_nqn(
+ tmp.string_value().c_str()))
+ return false;
+ }
+ } else {
+ log_warnx("\"host-nqn\" property of "
+ "auth-group \"%s\" is not an array or string",
+ name);
+ return false;
+ }
+ }
+
if (key == "initiator-name") {
if (obj.type() == UCL_STRING) {
if (!auth_group_add_initiator_name(
@@ -739,6 +904,222 @@ uclparse_portal_group(const char *name, const ucl::Ucl &top)
}
static bool
+uclparse_transport_listen_obj(const char *pg_name, const ucl::Ucl &top)
+{
+ for (const auto &obj : top) {
+ std::string key = obj.key();
+
+ if (key.empty()) {
+ log_warnx("missing protocol for \"listen\" "
+ "property of transport-group \"%s\"", pg_name);
+ return false;
+ }
+
+ if (key == "tcp") {
+ if (obj.type() == UCL_STRING) {
+ if (!transport_group_add_listen_tcp(
+ obj.string_value().c_str()))
+ return false;
+ } else if (obj.type() == UCL_ARRAY) {
+ for (const auto &tmp : obj) {
+ if (!transport_group_add_listen_tcp(
+ tmp.string_value().c_str()))
+ return false;
+ }
+ }
+ } else if (key == "discovery-tcp") {
+ if (obj.type() == UCL_STRING) {
+ if (!transport_group_add_listen_discovery_tcp(
+ obj.string_value().c_str()))
+ return false;
+ } else if (obj.type() == UCL_ARRAY) {
+ for (const auto &tmp : obj) {
+ if (!transport_group_add_listen_discovery_tcp(
+ tmp.string_value().c_str()))
+ return false;
+ }
+ }
+ } else {
+ log_warnx("invalid listen protocol \"%s\" for "
+ "transport-group \"%s\"", key.c_str(), pg_name);
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool
+uclparse_transport_group(const char *name, const ucl::Ucl &top)
+{
+ if (!transport_group_start(name))
+ return false;
+
+ scope_exit finisher(portal_group_finish);
+ for (const auto &obj : top) {
+ std::string key = obj.key();
+
+ if (key == "discovery-auth-group") {
+ if (obj.type() != UCL_STRING) {
+ log_warnx("\"discovery-auth-group\" property "
+ "of transport-group \"%s\" is not a string",
+ name);
+ return false;
+ }
+
+ if (!portal_group_set_discovery_auth_group(
+ obj.string_value().c_str()))
+ return false;
+ }
+
+ if (key == "discovery-filter") {
+ if (obj.type() != UCL_STRING) {
+ log_warnx("\"discovery-filter\" property of "
+ "transport-group \"%s\" is not a string",
+ name);
+ return false;
+ }
+
+ if (!portal_group_set_filter(
+ obj.string_value().c_str()))
+ return false;
+ }
+
+ if (key == "listen") {
+ if (obj.type() != UCL_OBJECT) {
+ log_warnx("\"listen\" property of "
+ "transport-group \"%s\" is not an object",
+ name);
+ return false;
+ }
+ if (!uclparse_transport_listen_obj(name, obj))
+ return false;
+ }
+
+ if (key == "options") {
+ if (obj.type() != UCL_OBJECT) {
+ log_warnx("\"options\" property of transport group "
+ "\"%s\" is not an object", name);
+ return false;
+ }
+
+ for (const auto &tmp : obj) {
+ if (!portal_group_add_option(
+ tmp.key().c_str(),
+ tmp.forced_string_value().c_str()))
+ return false;
+ }
+ }
+
+ if (key == "dscp") {
+ if (!uclparse_dscp("transport", name, obj))
+ return false;
+ }
+
+ if (key == "pcp") {
+ if (!uclparse_pcp("transport", name, obj))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool
+uclparse_controller(const char *name, const ucl::Ucl &top)
+{
+ if (!controller_start(name))
+ return false;
+
+ scope_exit finisher(target_finish);
+ for (const auto &obj : top) {
+ std::string key = obj.key();
+
+ if (key == "auth-group") {
+ if (obj.type() != UCL_STRING) {
+ log_warnx("\"auth-group\" property of "
+ "controller \"%s\" is not a string", name);
+ return false;
+ }
+
+ if (!target_set_auth_group(obj.string_value().c_str()))
+ return false;
+ }
+
+ if (key == "auth-type") {
+ if (obj.type() != UCL_STRING) {
+ log_warnx("\"auth-type\" property of "
+ "controller \"%s\" is not a string", name);
+ return false;
+ }
+
+ if (!target_set_auth_type(obj.string_value().c_str()))
+ return false;
+ }
+
+ if (key == "host-address") {
+ if (obj.type() == UCL_STRING) {
+ if (!controller_add_host_address(
+ obj.string_value().c_str()))
+ return false;
+ } else if (obj.type() == UCL_ARRAY) {
+ for (const auto &tmp : obj) {
+ if (!controller_add_host_address(
+ tmp.string_value().c_str()))
+ return false;
+ }
+ } else {
+ log_warnx("\"host-address\" property of "
+ "controller \"%s\" is not an array or "
+ "string", name);
+ return false;
+ }
+ }
+
+ if (key == "host-nqn") {
+ if (obj.type() == UCL_STRING) {
+ if (!controller_add_host_nqn(
+ obj.string_value().c_str()))
+ return false;
+ } else if (obj.type() == UCL_ARRAY) {
+ for (const auto &tmp : obj) {
+ if (!controller_add_host_nqn(
+ tmp.string_value().c_str()))
+ return false;
+ }
+ } else {
+ log_warnx("\"host-nqn\" property of "
+ "controller \"%s\" is not an array or "
+ "string", name);
+ return false;
+ }
+ }
+
+ if (key == "transport-group") {
+ if (obj.type() == UCL_ARRAY) {
+ for (const auto &tmp : obj) {
+ if (!uclparse_controller_transport_group(name,
+ tmp))
+ return false;
+ }
+ } else {
+ if (!uclparse_controller_transport_group(name,
+ obj))
+ return false;
+ }
+ }
+
+ if (key == "namespace") {
+ for (const auto &tmp : obj) {
+ if (!uclparse_controller_namespace(name, tmp))
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+static bool
uclparse_target(const char *name, const ucl::Ucl &top)
{
if (!target_start(name))
@@ -1037,7 +1418,7 @@ uclparse_conf(const char *path)
bool parsed;
try {
parsed = uclparse_toplevel(top);
- } catch (std::bad_alloc) {
+ } catch (std::bad_alloc &) {
log_warnx("failed to allocate memory parsing %s", path);
parsed = false;
} catch (...) {