git: 7e844dcad22b - main - ctld: Add abstractions to support multiple target protocols

From: John Baldwin <jhb_at_FreeBSD.org>
Date: Wed, 06 Aug 2025 20:10:00 UTC
The branch main has been updated by jhb:

URL: https://cgit.FreeBSD.org/src/commit/?id=7e844dcad22bdbbdfa9c73271fe46b9ef963d34c

commit 7e844dcad22bdbbdfa9c73271fe46b9ef963d34c
Author:     John Baldwin <jhb@FreeBSD.org>
AuthorDate: 2025-08-06 19:56:56 +0000
Commit:     John Baldwin <jhb@FreeBSD.org>
CommitDate: 2025-08-06 19:56:56 +0000

    ctld: Add abstractions to support multiple target protocols
    
    This is a prerequisite for adding NVMe over Fabrics support.
    
    Convert portal_group, portal_group_port, and target into abstract
    classes with virtual methods to support protocol-specific methods.
    
    Add new iscsi_portal_group, iscsi_port, iscsi_portal and iscsi_target
    subclasses in a new iscsi.cc file and move some iSCSI-specific logic
    there.  Rename ctld_connection to iscsi_connection and move it to a
    new iscsi.hh header.  Move iscsi_connection methods out of ctld.cc and
    kernel.cc into iscsi.cc.
    
    Reviewed by:    imp
    Sponsored by:   Chelsio Communications
    Differential Revision:  https://reviews.freebsd.org/D48772
---
 usr.sbin/ctld/Makefile     |   6 +-
 usr.sbin/ctld/conf.cc      |   3 +-
 usr.sbin/ctld/ctld.cc      | 349 ++++++++-----------------------
 usr.sbin/ctld/ctld.hh      | 127 ++++++------
 usr.sbin/ctld/discovery.cc |   5 +-
 usr.sbin/ctld/iscsi.cc     | 508 +++++++++++++++++++++++++++++++++++++++++++++
 usr.sbin/ctld/iscsi.hh     |  78 +++++++
 usr.sbin/ctld/kernel.cc    | 193 +++++------------
 usr.sbin/ctld/login.cc     |  15 +-
 9 files changed, 799 insertions(+), 485 deletions(-)

diff --git a/usr.sbin/ctld/Makefile b/usr.sbin/ctld/Makefile
index 26cc03c036cb..ad3a2661794a 100644
--- a/usr.sbin/ctld/Makefile
+++ b/usr.sbin/ctld/Makefile
@@ -5,7 +5,7 @@ CFLAGS+=-I${SRCTOP}/contrib/libucl/include
 
 PACKAGE=	ctl
 PROG_CXX=	ctld
-SRCS=		ctld.cc conf.cc discovery.cc isns.cc kernel.cc
+SRCS=		ctld.cc conf.cc discovery.cc iscsi.cc isns.cc kernel.cc
 SRCS+=		login.cc parse.y token.l y.tab.h uclparse.cc
 CFLAGS+=	-I${.CURDIR}
 CFLAGS+=	-I${SRCTOP}/sys
@@ -25,10 +25,6 @@ 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..d8f941e0bc52 100644
--- a/usr.sbin/ctld/conf.cc
+++ b/usr.sbin/ctld/conf.cc
@@ -175,7 +175,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
diff --git a/usr.sbin/ctld/ctld.cc b/usr.sbin/ctld/ctld.cc
index 29fc95c76a4f..4821bdb030c1 100644
--- a/usr.sbin/ctld/ctld.cc
+++ b/usr.sbin/ctld/ctld.cc
@@ -58,13 +58,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 +66,6 @@ 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,
-};
 
 static void
 usage(void)
@@ -477,8 +457,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 +466,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);
@@ -531,7 +511,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;
@@ -598,25 +578,6 @@ portal_group::options() const
 	return (freebsd::nvlist_up(nvlist_clone(pg_options.get())));
 }
 
-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)
 {
@@ -627,14 +588,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 +605,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 +614,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 +625,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 +638,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 +652,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 +680,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 +718,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 +747,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 +1040,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 +1056,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);
@@ -1184,6 +1113,12 @@ portal_group::find_port(std::string_view target) const
 	return (it->second);
 }
 
+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)
 {
@@ -1198,7 +1133,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 +1160,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 +1188,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);
 	}
 
@@ -1295,42 +1213,11 @@ target::add_lun(u_int id, const char *lun_name)
 	return (true);
 }
 
-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 +1230,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 +1270,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 +1280,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 +1331,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 +1339,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)
@@ -1714,59 +1596,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()
 {
@@ -1882,23 +1711,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 +1735,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 +1781,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());
@@ -2038,7 +1862,7 @@ 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();
 	}
 
 	/* Deregister on removed iSNS servers. */
@@ -2225,7 +2049,7 @@ conf::apply(struct conf *oldconf)
 	return (cumulated_error);
 }
 
-static bool
+bool
 timed_out(void)
 {
 
@@ -2390,17 +2214,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(fd, host, client_sa);
 	log_debugx("nothing more to do; exiting");
 	exit(0);
 }
@@ -2612,8 +2426,7 @@ 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->verify()) {
@@ -2644,7 +2457,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 +2466,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 53479aa09d9d..7842f1bb5715 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;
@@ -130,18 +129,28 @@ private:
 
 using auth_group_sp = std::shared_ptr<auth_group>;
 
+enum class portal_protocol {
+	ISCSI,
+	ISER
+};
+
 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(int fd, const char *host,
+	    const struct sockaddr *client_sa) = 0;
 
 	portal_group *portal_group() { 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 +158,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,8 +176,10 @@ 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;
@@ -188,17 +200,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 +228,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 +275,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 +291,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 +366,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 +386,36 @@ 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_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_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; }
 
 	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 +423,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)) {}
@@ -528,50 +555,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;
*** 991 LINES SKIPPED ***