git: 6acc7afa34aa - main - ctld: Convert struct port to a hierarchy of C++ classes

From: John Baldwin <jhb_at_FreeBSD.org>
Date: Mon, 04 Aug 2025 19:46:56 UTC
The branch main has been updated by jhb:

URL: https://cgit.FreeBSD.org/src/commit/?id=6acc7afa34aa5da8d9132e927f2026e594e73701

commit 6acc7afa34aa5da8d9132e927f2026e594e73701
Author:     John Baldwin <jhb@FreeBSD.org>
AuthorDate: 2025-08-04 19:38:07 +0000
Commit:     John Baldwin <jhb@FreeBSD.org>
CommitDate: 2025-08-04 19:38:07 +0000

    ctld: Convert struct port to a hierarchy of C++ classes
    
    The existing C struct port was used to describe three types of ports:
    iSCSI ports associated with a portal_group, ioctl ports, and
    "physical" ports associated with a kernel device.  This change chooses
    to split these out into separate sub-classes of an abstract port base
    class.  Virtual methods are used in a few places such as sending the
    class-specific CTL ioctls for creating and removing CTL kernel ports.
    
    For ownership purposes, a struct conf instance "owns" each port via a
    std::unique_ptr<> in a std::unordered_map<> indexed by name.  Other
    objects such as targets and portal_groups can also contain collections
    of ports (targets hold a std::list of pointers, portal groups hold a
    std::unordered_map<> indexed by target names).  One
    not-so-straightforward case is that if a new port fails to register,
    it is removed from the configuration.  In that case, these other
    references also have to be removed explicitly.
    
    Sponsored by:   Chelsio Communications
    Pull Request:   https://github.com/freebsd/freebsd-src/pull/1794
---
 usr.sbin/ctld/conf.cc      |   5 +-
 usr.sbin/ctld/ctld.cc      | 332 ++++++++++++++++++++-----------------------
 usr.sbin/ctld/ctld.hh      | 112 ++++++++++-----
 usr.sbin/ctld/discovery.cc |  24 ++--
 usr.sbin/ctld/kernel.cc    | 342 ++++++++++++++++++++++++---------------------
 usr.sbin/ctld/login.cc     |   6 +-
 6 files changed, 435 insertions(+), 386 deletions(-)

diff --git a/usr.sbin/ctld/conf.cc b/usr.sbin/ctld/conf.cc
index a30bf72c0469..2bf7b99409de 100644
--- a/usr.sbin/ctld/conf.cc
+++ b/usr.sbin/ctld/conf.cc
@@ -571,7 +571,6 @@ target_add_portal_group(const char *pg_name, const char *ag_name)
 {
 	struct portal_group *pg;
 	auth_group_sp ag;
-	struct port *p;
 
 	pg = portal_group_find(conf, pg_name);
 	if (pg == NULL) {
@@ -589,13 +588,11 @@ target_add_portal_group(const char *pg_name, const char *ag_name)
 		}
 	}
 
-	p = port_new(conf, target, pg);
-	if (p == NULL) {
+	if (!port_new(conf, target, pg, std::move(ag))) {
 		log_warnx("can't link portal-group \"%s\" to target \"%s\"",
 		    pg_name, target->t_name);
 		return (false);
 	}
-	p->p_auth_group = std::move(ag);
 	return (true);
 }
 
diff --git a/usr.sbin/ctld/ctld.cc b/usr.sbin/ctld/ctld.cc
index 07592a07c019..e8770cb9315d 100644
--- a/usr.sbin/ctld/ctld.cc
+++ b/usr.sbin/ctld/ctld.cc
@@ -103,7 +103,6 @@ conf_new(void)
 	conf = new struct conf();
 	TAILQ_INIT(&conf->conf_luns);
 	TAILQ_INIT(&conf->conf_targets);
-	TAILQ_INIT(&conf->conf_ports);
 	TAILQ_INIT(&conf->conf_portal_groups);
 	TAILQ_INIT(&conf->conf_isns);
 
@@ -134,7 +133,6 @@ conf_delete(struct conf *conf)
 		portal_group_delete(pg);
 	TAILQ_FOREACH_SAFE(is, &conf->conf_isns, i_next, istmp)
 		isns_delete(is);
-	assert(TAILQ_EMPTY(&conf->conf_ports));
 	free(conf->conf_pidfile_path);
 	delete conf;
 }
@@ -455,7 +453,6 @@ portal_group_new(struct conf *conf, const char *name)
 	pg = new portal_group();
 	pg->pg_name = checked_strdup(name);
 	pg->pg_options = nvlist_create(0);
-	TAILQ_INIT(&pg->pg_ports);
 	pg->pg_conf = conf;
 	pg->pg_tag = 0;		/* Assigned later in conf_apply(). */
 	pg->pg_dscp = -1;
@@ -468,10 +465,6 @@ portal_group_new(struct conf *conf, const char *name)
 void
 portal_group_delete(struct portal_group *pg)
 {
-	struct port *port, *tport;
-
-	TAILQ_FOREACH_SAFE(port, &pg->pg_ports, p_pgs, tport)
-		port_delete(port);
 	TAILQ_REMOVE(&pg->pg_conf->conf_portal_groups, pg, pg_next);
 
 	nvlist_destroy(pg->pg_options);
@@ -625,7 +618,6 @@ isns_do_register(struct isns *isns, int s, const char *hostname)
 	struct conf *conf = isns->i_conf;
 	struct target *target;
 	struct portal_group *pg;
-	struct port *port;
 	uint32_t error;
 
 	isns_req req(ISNS_FUNC_DEVATTRREG, ISNS_FLAG_CLIENT);
@@ -647,8 +639,9 @@ isns_do_register(struct isns *isns, int s, const char *hostname)
 		req.add_32(33, 1); /* 1 -- Target*/
 		if (target->t_alias != NULL)
 			req.add_str(34, target->t_alias);
-		TAILQ_FOREACH(port, &target->t_ports, p_ts) {
-			if ((pg = port->p_portal_group) == NULL)
+		for (const port *port : target->t_ports) {
+			pg = port->portal_group();
+			if (pg == nullptr)
 				continue;
 			req.add_32(51, pg->pg_tag);
 			for (const portal_up &portal : pg->pg_portals) {
@@ -801,12 +794,6 @@ isns_deregister(struct isns *isns)
 	set_timeout(0, false);
 }
 
-pport::~pport()
-{
-	if (pp_port != nullptr)
-		port_delete(pp_port);
-}
-
 bool
 kports::add_port(const char *name, uint32_t ctl_port)
 {
@@ -834,151 +821,129 @@ kports::find_port(std::string_view name)
 	return (&it->second);
 }
 
-struct port *
-port_new(struct conf *conf, struct target *target, struct portal_group *pg)
-{
-	struct port *port;
-	char *name;
-	int ret;
-
-	ret = asprintf(&name, "%s-%s", pg->pg_name, target->t_name);
-	if (ret <= 0)
-		log_err(1, "asprintf");
-	if (port_find(conf, name) != NULL) {
-		log_warnx("duplicate port \"%s\"", name);
-		free(name);
-		return (NULL);
-	}
-	port = new struct port();
-	port->p_conf = conf;
-	port->p_name = name;
-	TAILQ_INSERT_TAIL(&conf->conf_ports, port, p_next);
-	TAILQ_INSERT_TAIL(&target->t_ports, port, p_ts);
-	port->p_target = target;
-	TAILQ_INSERT_TAIL(&pg->pg_ports, port, p_pgs);
-	port->p_portal_group = pg;
-	return (port);
+port::port(struct target *target) :
+	p_target(target)
+{
+	target->t_ports.push_back(this);
 }
 
-struct port *
-port_new_ioctl(struct conf *conf, struct kports &kports, struct target *target,
-    int pp, int vp)
+void
+port::clear_references()
 {
-	struct pport *pport;
-	struct port *port;
-	char *pname;
-	char *name;
-	int ret;
-
-	ret = asprintf(&pname, "ioctl/%d/%d", pp, vp);
-	if (ret <= 0) {
-		log_err(1, "asprintf");
-		return (NULL);
-	}
-
-	pport = kports.find_port(pname);
-	if (pport != NULL) {
-		free(pname);
-		return (port_new_pp(conf, target, pport));
-	}
-
-	ret = asprintf(&name, "%s-%s", pname, target->t_name);
-	free(pname);
+	p_target->t_ports.remove(this);
+}
 
-	if (ret <= 0)
-		log_err(1, "asprintf");
-	if (port_find(conf, name) != NULL) {
-		log_warnx("duplicate port \"%s\"", name);
-		free(name);
-		return (NULL);
-	}
-	port = new struct port();
-	port->p_conf = conf;
-	port->p_name = name;
-	port->p_ioctl_port = true;
-	port->p_ioctl_pp = pp;
-	port->p_ioctl_vp = vp;
-	TAILQ_INSERT_TAIL(&conf->conf_ports, port, p_next);
-	TAILQ_INSERT_TAIL(&target->t_ports, port, p_ts);
-	port->p_target = target;
-	return (port);
+portal_group_port::portal_group_port(struct target *target,
+    struct portal_group *pg, auth_group_sp ag) :
+	port(target), p_auth_group(ag), p_portal_group(pg)
+{
+	pg->pg_ports.emplace(target->t_name, this);
 }
 
-struct port *
-port_new_pp(struct conf *conf, struct target *target, struct pport *pp)
+portal_group_port::portal_group_port(struct target *target,
+    struct portal_group *pg, uint32_t ctl_port) :
+	port(target), p_portal_group(pg)
 {
-	struct port *port;
-	char *name;
-	int ret;
+	p_ctl_port = ctl_port;
+	pg->pg_ports.emplace(target->t_name, this);
+}
 
-	ret = asprintf(&name, "%s-%s", pp->name(), target->t_name);
-	if (ret <= 0)
-		log_err(1, "asprintf");
-	if (port_find(conf, name) != NULL) {
-		log_warnx("duplicate port \"%s\"", name);
-		free(name);
-		return (NULL);
-	}
-	port = new struct port();
-	port->p_conf = conf;
-	port->p_name = name;
-	TAILQ_INSERT_TAIL(&conf->conf_ports, port, p_next);
-	TAILQ_INSERT_TAIL(&target->t_ports, port, p_ts);
-	port->p_target = target;
-	pp->link(port);
-	return (port);
+bool
+portal_group_port::is_dummy() const
+{
+	if (p_portal_group->pg_foreign)
+		return (true);
+	if (p_portal_group->pg_portals.empty())
+		return (true);
+	return (false);
 }
 
-struct port *
-port_find(const struct conf *conf, const char *name)
+void
+portal_group_port::clear_references()
 {
-	struct port *port;
+	auto it = p_portal_group->pg_ports.find(p_target->t_name);
+	p_portal_group->pg_ports.erase(it);
+	port::clear_references();
+}
 
-	TAILQ_FOREACH(port, &conf->conf_ports, p_next) {
-		if (strcasecmp(port->p_name, name) == 0)
-			return (port);
+bool
+port_new(struct conf *conf, struct target *target, struct portal_group *pg,
+    auth_group_sp ag)
+{
+	std::string name = freebsd::stringf("%s-%s", pg->pg_name,
+	    target->t_name);
+	const auto &pair = conf->conf_ports.try_emplace(name,
+	    std::make_unique<portal_group_port>(target, pg, ag));
+	if (!pair.second) {
+		log_warnx("duplicate port \"%s\"", name.c_str());
+		return (false);
 	}
 
-	return (NULL);
+	return (true);
 }
 
-struct port *
-port_find_in_pg(const struct portal_group *pg, const char *target)
+bool
+port_new(struct conf *conf, struct target *target, struct portal_group *pg,
+    uint32_t ctl_port)
 {
-	struct port *port;
-
-	TAILQ_FOREACH(port, &pg->pg_ports, p_pgs) {
-		if (strcasecmp(port->p_target->t_name, target) == 0)
-			return (port);
+	std::string name = freebsd::stringf("%s-%s", pg->pg_name,
+	    target->t_name);
+	const auto &pair = conf->conf_ports.try_emplace(name,
+	    std::make_unique<portal_group_port>(target, pg, ctl_port));
+	if (!pair.second) {
+		log_warnx("duplicate port \"%s\"", name.c_str());
+		return (false);
 	}
 
-	return (NULL);
+	return (true);
 }
 
-void
-port_delete(struct port *port)
+static bool
+port_new_pp(struct conf *conf, struct target *target, struct pport *pp)
 {
+	std::string name = freebsd::stringf("%s-%s", pp->name(),
+	    target->t_name);
+	const auto &pair = conf->conf_ports.try_emplace(name,
+	    std::make_unique<kernel_port>(target, pp));
+	if (!pair.second) {
+		log_warnx("duplicate port \"%s\"", name.c_str());
+		return (false);
+	}
 
-	if (port->p_portal_group)
-		TAILQ_REMOVE(&port->p_portal_group->pg_ports, port, p_pgs);
-	if (port->p_target)
-		TAILQ_REMOVE(&port->p_target->t_ports, port, p_ts);
-	TAILQ_REMOVE(&port->p_conf->conf_ports, port, p_next);
-	free(port->p_name);
-	free(port);
+	pp->link();
+	return (true);
 }
 
-bool
-port_is_dummy(struct port *port)
+static bool
+port_new_ioctl(struct conf *conf, struct kports &kports, struct target *target,
+    int pp, int vp)
 {
+	struct pport *pport;
 
-	if (port->p_portal_group) {
-		if (port->p_portal_group->pg_foreign)
-			return (true);
-		if (port->p_portal_group->pg_portals.empty())
-			return (true);
+	std::string pname = freebsd::stringf("ioctl/%d/%d", pp, vp);
+
+	pport = kports.find_port(pname);
+	if (pport != NULL)
+		return (port_new_pp(conf, target, pport));
+
+	std::string name = pname + "-" + target->t_name;
+	const auto &pair = conf->conf_ports.try_emplace(name,
+	    std::make_unique<ioctl_port>(target, pp, vp));
+	if (!pair.second) {
+		log_warnx("duplicate port \"%s\"", name.c_str());
+		return (false);
 	}
-	return (false);
+
+	return (true);
+}
+
+struct port *
+port_find_in_pg(const struct portal_group *pg, const char *target)
+{
+	auto it = pg->pg_ports.find(target);
+	if (it == pg->pg_ports.end())
+		return (nullptr);
+	return (it->second);
 }
 
 struct target *
@@ -1006,7 +971,6 @@ target_new(struct conf *conf, const char *name)
 		targ->t_name[i] = tolower(targ->t_name[i]);
 
 	targ->t_conf = conf;
-	TAILQ_INIT(&targ->t_ports);
 	TAILQ_INSERT_TAIL(&conf->conf_targets, targ, t_next);
 
 	return (targ);
@@ -1015,10 +979,6 @@ target_new(struct conf *conf, const char *name)
 void
 target_delete(struct target *targ)
 {
-	struct port *port, *tport;
-
-	TAILQ_FOREACH_SAFE(port, &targ->t_ports, p_ts, tport)
-		port_delete(port);
 	TAILQ_REMOVE(&targ->t_conf->conf_targets, targ, t_next);
 
 	free(targ->t_pport);
@@ -1261,10 +1221,10 @@ conf_verify(struct conf *conf)
 			    "default");
 			assert(targ->t_auth_group != NULL);
 		}
-		if (TAILQ_EMPTY(&targ->t_ports)) {
+		if (targ->t_ports.empty()) {
 			pg = portal_group_find(conf, "default");
 			assert(pg != NULL);
-			port_new(conf, targ, pg);
+			port_new(conf, targ, pg, nullptr);
 		}
 		found = false;
 		for (i = 0; i < MAX_LUNS; i++) {
@@ -1293,14 +1253,14 @@ conf_verify(struct conf *conf)
 			pg->pg_discovery_filter = PG_FILTER_NONE;
 
 		if (pg->pg_redirection != NULL) {
-			if (!TAILQ_EMPTY(&pg->pg_ports)) {
+			if (!pg->pg_ports.empty()) {
 				log_debugx("portal-group \"%s\" assigned "
 				    "to target, but configured "
 				    "for redirection",
 				    pg->pg_name);
 			}
 			pg->pg_unassigned = false;
-		} else if (!TAILQ_EMPTY(&pg->pg_ports)) {
+		} else if (!pg->pg_ports.empty()) {
 			pg->pg_unassigned = false;
 		} else {
 			if (strcmp(pg->pg_name, "default") != 0)
@@ -1453,7 +1413,6 @@ conf_apply(struct conf *oldconf, struct conf *newconf)
 {
 	struct lun *oldlun, *newlun, *tmplun;
 	struct portal_group *oldpg, *newpg;
-	struct port *oldport, *newport, *tmpport;
 	struct isns *oldns, *newns;
 	int changed, cumulated_error = 0, error;
 
@@ -1517,17 +1476,19 @@ conf_apply(struct conf *oldconf, struct conf *newconf)
 	 * First, remove any ports present in the old configuration
 	 * and missing in the new one.
 	 */
-	TAILQ_FOREACH_SAFE(oldport, &oldconf->conf_ports, p_next, tmpport) {
-		if (port_is_dummy(oldport))
+	for (const auto &kv : oldconf->conf_ports) {
+		const std::string &name = kv.first;
+		port *oldport = kv.second.get();
+
+		if (oldport->is_dummy())
 			continue;
-		newport = port_find(newconf, oldport->p_name);
-		if (newport != NULL && !port_is_dummy(newport))
+		const auto it = newconf->conf_ports.find(name);
+		if (it != newconf->conf_ports.end() &&
+		    !it->second->is_dummy())
 			continue;
-		log_debugx("removing port \"%s\"", oldport->p_name);
-		error = kernel_port_remove(oldport);
-		if (error != 0) {
-			log_warnx("failed to remove port %s",
-			    oldport->p_name);
+		log_debugx("removing port \"%s\"", name.c_str());
+		if (!oldport->kernel_remove()) {
+			log_warnx("failed to remove port %s", name.c_str());
 			/*
 			 * XXX: Uncomment after fixing the root cause.
 			 *
@@ -1640,30 +1601,46 @@ conf_apply(struct conf *oldconf, struct conf *newconf)
 	/*
 	 * Now add new ports or modify existing ones.
 	 */
-	TAILQ_FOREACH_SAFE(newport, &newconf->conf_ports, p_next, tmpport) {
-		if (port_is_dummy(newport))
-			continue;
-		oldport = port_find(oldconf, newport->p_name);
+	for (auto it = newconf->conf_ports.begin();
+	     it != newconf->conf_ports.end(); ) {
+		const std::string &name = it->first;
+		port *newport = it->second.get();
 
-		if (oldport == NULL || port_is_dummy(oldport)) {
-			log_debugx("adding port \"%s\"", newport->p_name);
-			error = kernel_port_add(newport);
-		} else {
-			log_debugx("updating port \"%s\"", newport->p_name);
-			newport->p_ctl_port = oldport->p_ctl_port;
-			error = kernel_port_update(newport, oldport);
+		if (newport->is_dummy()) {
+			it++;
+			continue;
 		}
-		if (error != 0) {
-			log_warnx("failed to %s port %s",
-			    (oldport == NULL) ? "add" : "update",
-			    newport->p_name);
-			if (oldport == NULL || port_is_dummy(oldport))
-				port_delete(newport);
-			/*
-			 * XXX: Uncomment after fixing the root cause.
-			 *
-			 * cumulated_error++;
-			 */
+		const auto oldit = oldconf->conf_ports.find(name);
+		if (oldit == oldconf->conf_ports.end() ||
+		    oldit->second->is_dummy()) {
+			log_debugx("adding port \"%s\"", name.c_str());
+			if (!newport->kernel_add()) {
+				log_warnx("failed to add port %s",
+				    name.c_str());
+
+				/*
+				 * XXX: Uncomment after fixing the
+				 * root cause.
+				 *
+				 * cumulated_error++;
+				 */
+
+				/*
+				 * conf "owns" the port, but other
+				 * objects contain pointers to this
+				 * port that must be removed before
+				 * deleting the port.
+				 */
+				newport->clear_references();
+				it = newconf->conf_ports.erase(it);
+			} else
+				it++;
+		} else {
+			log_debugx("updating port \"%s\"", name.c_str());
+			if (!newport->kernel_update(oldit->second.get()))
+				log_warnx("failed to update port %s",
+				    name.c_str());
+			it++;
 		}
 	}
 
@@ -2134,7 +2111,6 @@ new_pports_from_conf(struct conf *conf, struct kports &kports)
 {
 	struct target *targ;
 	struct pport *pp;
-	struct port *tp;
 	int ret, i_pp, i_vp;
 
 	TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
@@ -2143,8 +2119,7 @@ new_pports_from_conf(struct conf *conf, struct kports &kports)
 
 		ret = sscanf(targ->t_pport, "ioctl/%d/%d", &i_pp, &i_vp);
 		if (ret > 0) {
-			tp = port_new_ioctl(conf, kports, targ, i_pp, i_vp);
-			if (tp == NULL) {
+			if (!port_new_ioctl(conf, kports, targ, i_pp, i_vp)) {
 				log_warnx("can't create new ioctl port "
 				    "for target \"%s\"", targ->t_name);
 				return (false);
@@ -2165,8 +2140,7 @@ new_pports_from_conf(struct conf *conf, struct kports &kports)
 			    targ->t_pport, targ->t_name);
 			return (false);
 		}
-		tp = port_new_pp(conf, targ, pp);
-		if (tp == NULL) {
+		if (!port_new_pp(conf, targ, pp)) {
 			log_warnx("can't link port \"%s\" to target \"%s\"",
 			    targ->t_pport, targ->t_name);
 			return (false);
diff --git a/usr.sbin/ctld/ctld.hh b/usr.sbin/ctld/ctld.hh
index ee50acf1f3e8..3d9c30b31977 100644
--- a/usr.sbin/ctld/ctld.hh
+++ b/usr.sbin/ctld/ctld.hh
@@ -57,6 +57,8 @@
 #define	MAX_LUNS			1024
 #define	SOCKBUF_SIZE			1048576
 
+struct port;
+
 struct auth {
 	auth(std::string_view secret) : a_secret(secret) {}
 	auth(std::string_view secret, std::string_view mutual_user,
@@ -168,7 +170,7 @@ struct portal_group {
 	bool				pg_foreign = false;
 	bool				pg_unassigned = false;
 	std::list<portal_up>	        pg_portals;
-	TAILQ_HEAD(, port)		pg_ports;
+	std::unordered_map<std::string, port *> pg_ports;
 	char				*pg_offload = nullptr;
 	char				*pg_redirection = nullptr;
 	int				pg_dscp;
@@ -178,22 +180,79 @@ struct portal_group {
 };
 
 struct port {
-	TAILQ_ENTRY(port)		p_next;
-	TAILQ_ENTRY(port)		p_pgs;
-	TAILQ_ENTRY(port)		p_ts;
-	struct conf			*p_conf;
-	char				*p_name;
-	auth_group_sp			p_auth_group;
-	struct portal_group		*p_portal_group = nullptr;
-	struct pport			*p_pport = nullptr;
+	port(struct target *target);
+	virtual ~port() = default;
+
+	struct target *target() const { return p_target; }
+	virtual struct auth_group *auth_group() const { return nullptr; }
+	virtual struct portal_group *portal_group() const { return nullptr; }
+
+	virtual bool is_dummy() const { return true; }
+
+	virtual void clear_references();
+
+	bool kernel_add();
+	bool kernel_update(const port *oport);
+	bool kernel_remove();
+
+	virtual bool kernel_create_port() = 0;
+	virtual bool kernel_remove_port() = 0;
+
+protected:
 	struct target			*p_target;
 
-	bool				p_ioctl_port = false;
-	int				p_ioctl_pp = 0;
-	int				p_ioctl_vp = 0;
 	uint32_t			p_ctl_port = 0;
 };
 
+struct portal_group_port final : 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,
+	    uint32_t ctl_port);
+	~portal_group_port() override = default;
+
+	struct auth_group *auth_group() const override
+	{ return p_auth_group.get(); }
+	struct portal_group *portal_group() const override
+	{ return p_portal_group; }
+
+	bool is_dummy() const override;
+
+	void clear_references() override;
+
+	bool kernel_create_port() override;
+	bool kernel_remove_port() override;
+
+private:
+	auth_group_sp			p_auth_group;
+	struct portal_group		*p_portal_group;
+};
+
+struct ioctl_port final : public port {
+	ioctl_port(struct target *target, int pp, int vp) :
+		port(target), p_ioctl_pp(pp), p_ioctl_vp(vp) {}
+	~ioctl_port() override = default;
+
+	bool kernel_create_port() override;
+	bool kernel_remove_port() override;
+
+private:
+	int				p_ioctl_pp;
+	int				p_ioctl_vp;
+};
+
+struct kernel_port final : public port {
+	kernel_port(struct target *target, struct pport *pp) :
+		port(target), p_pport(pp) {}
+	~kernel_port() override = default;
+
+	bool kernel_create_port() override;
+	bool kernel_remove_port() override;
+
+private:
+	struct pport			*p_pport;
+};
+
 struct lun {
 	TAILQ_ENTRY(lun)		l_next;
 	struct conf			*l_conf;
@@ -216,7 +275,7 @@ struct target {
 	struct conf			*t_conf;
 	struct lun			*t_luns[MAX_LUNS] = {};
 	auth_group_sp			t_auth_group;
-	TAILQ_HEAD(, port)		t_ports;
+	std::list<port *>		t_ports;
 	char				*t_name;
 	char				*t_alias;
 	char				*t_redirection;
@@ -237,7 +296,7 @@ struct conf {
 	TAILQ_HEAD(, lun)		conf_luns;
 	TAILQ_HEAD(, target)		conf_targets;
 	std::unordered_map<std::string, auth_group_sp> conf_auth_groups;
-	TAILQ_HEAD(, port)		conf_ports;
+	std::unordered_map<std::string, std::unique_ptr<port>> conf_ports;
 	TAILQ_HEAD(, portal_group)	conf_portal_groups;
 	TAILQ_HEAD(, isns)		conf_isns;
 	int				conf_isns_period;
@@ -264,19 +323,17 @@ private:
 struct pport {
 	pport(std::string_view name, uint32_t ctl_port) : pp_name(name),
 	    pp_ctl_port(ctl_port) {}
-	~pport();
 
 	const char *name() const { return pp_name.c_str(); }
 	uint32_t ctl_port() const { return pp_ctl_port; }
 
-	bool linked() const { return pp_port != nullptr; }
-	void link(struct port *port) { pp_port = port; }
+	bool linked() const { return pp_linked; }
+	void link() { pp_linked = true; }
 
 private:
-	struct port			*pp_port;
 	std::string			pp_name;
-
 	uint32_t			pp_ctl_port;
+	bool				pp_linked;
 };
 
 struct kports {
@@ -341,18 +398,12 @@ void			isns_register(struct isns *isns, struct isns *oldisns);
 void			isns_check(struct isns *isns);
 void			isns_deregister(struct isns *isns);
 
-struct port		*port_new(struct conf *conf, struct target *target,
-			    struct portal_group *pg);
-struct port		*port_new_ioctl(struct conf *conf,
-			    struct kports &kports, struct target *target,
-			    int pp, int vp);
-struct port		*port_new_pp(struct conf *conf, struct target *target,
-			    struct pport *pp);
-struct port		*port_find(const struct conf *conf, const char *name);
+bool			port_new(struct conf *conf, struct target *target,
+			    struct portal_group *pg, auth_group_sp ag);
+bool			port_new(struct conf *conf, struct target *target,
+			    struct portal_group *pg, uint32_t ctl_port);
 struct port		*port_find_in_pg(const struct portal_group *pg,
 			    const char *target);
-void			port_delete(struct port *port);
-bool			port_is_dummy(struct port *port);
 
 struct target		*target_new(struct conf *conf, const char *name);
 void			target_delete(struct target *target);
@@ -372,9 +423,6 @@ int			kernel_lun_add(struct lun *lun);
 int			kernel_lun_modify(struct lun *lun);
 int			kernel_lun_remove(struct lun *lun);
 void			kernel_handoff(struct ctld_connection *conn);
-int			kernel_port_add(struct port *port);
-int			kernel_port_update(struct port *port, struct port *old);
-int			kernel_port_remove(struct port *port);
 void			kernel_capsicate(void);
 
 #ifdef ICL_KERNEL_PROXY
diff --git a/usr.sbin/ctld/discovery.cc b/usr.sbin/ctld/discovery.cc
index 59bdadb29eb2..d52d4919ddc5 100644
--- a/usr.sbin/ctld/discovery.cc
+++ b/usr.sbin/ctld/discovery.cc
@@ -101,17 +101,17 @@ logout_new_response(struct pdu *request)
 static void
 discovery_add_target(struct keys *response_keys, const struct target *targ)
 {
-	struct port *port;
 	char *buf;
 	char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
 	const struct addrinfo *ai;
 	int ret;
 
 	keys_add(response_keys, "TargetName", targ->t_name);
-	TAILQ_FOREACH(port, &targ->t_ports, p_ts) {
-	    if (port->p_portal_group == NULL)
+	for (const port *port : targ->t_ports) {
+	    const struct portal_group *pg = port->portal_group();
+	    if (pg == nullptr)
 		continue;
-	    for (portal_up &portal : port->p_portal_group->pg_portals) {
+	    for (const portal_up &portal : pg->pg_portals) {
 		ai = portal->ai();
 		ret = getnameinfo(ai->ai_addr, ai->ai_addrlen,
 		    hbuf, sizeof(hbuf), sbuf, sizeof(sbuf),
@@ -125,13 +125,13 @@ discovery_add_target(struct keys *response_keys, const struct target *targ)
 			if (strcmp(hbuf, "0.0.0.0") == 0)
 				continue;
 			ret = asprintf(&buf, "%s:%s,%d", hbuf, sbuf,
-			    port->p_portal_group->pg_tag);
+			    pg->pg_tag);
 			break;
 		case AF_INET6:
 			if (strcmp(hbuf, "::") == 0)
 				continue;
 			ret = asprintf(&buf, "[%s]:%s,%d", hbuf, sbuf,
-			    port->p_portal_group->pg_tag);
+			    pg->pg_tag);
 			break;
 		default:
 			continue;
@@ -154,8 +154,8 @@ discovery_target_filtered_out(const struct ctld_connection *conn,
 	const struct auth *auth;
 	int error;
 
-	targ = port->p_target;
-	ag = port->p_auth_group.get();
+	targ = port->target();
+	ag = port->auth_group();
 	if (ag == nullptr)
 		ag = targ->t_auth_group.get();
 	pg = conn->conn_portal->portal_group();
@@ -228,12 +228,13 @@ discovery(struct ctld_connection *conn)
 	response_keys = keys_new();
 
 	if (strcmp(send_targets, "All") == 0) {
-		TAILQ_FOREACH(port, &pg->pg_ports, p_pgs) {
+		for (const auto &kv : pg->pg_ports) {
+			port = kv.second;
 			if (discovery_target_filtered_out(conn, port)) {
 				/* Ignore this target. */
 				continue;
 			}
-			discovery_add_target(response_keys, port->p_target);
+			discovery_add_target(response_keys, port->target());
 		}
 	} else {
 		port = port_find_in_pg(pg, send_targets);
@@ -244,7 +245,8 @@ discovery(struct ctld_connection *conn)
 			if (discovery_target_filtered_out(conn, port)) {
 				/* Ignore this target. */
 			} else {
-				discovery_add_target(response_keys, port->p_target);
+				discovery_add_target(response_keys,
+				    port->target());
 			}
 		}
 	}
diff --git a/usr.sbin/ctld/kernel.cc b/usr.sbin/ctld/kernel.cc
index eb3bfa1dc760..2325895a31fd 100644
--- a/usr.sbin/ctld/kernel.cc
+++ b/usr.sbin/ctld/kernel.cc
@@ -401,7 +401,6 @@ conf_new_from_kernel(struct kports &kports)
 	struct conf *conf = NULL;
 	struct target *targ;
 	struct portal_group *pg;
-	struct port *cp;
 	struct lun *cl;
 	struct ctl_lun_list list;
 	struct cctl_devlist_data devlist;
@@ -575,12 +574,10 @@ retry_port:
 			}
 		}
 		pg->pg_tag = port->cfiscsi_portal_group_tag;
-		cp = port_new(conf, targ, pg);
-		if (cp == NULL) {
+		if (!port_new(conf, targ, pg, port->port_id)) {
 			log_warnx("port_new failed");
 			continue;
 		}
-		cp->p_ctl_port = port->port_id;
 	}
 	while ((port = STAILQ_FIRST(&devlist.port_list))) {
 		STAILQ_REMOVE_HEAD(&devlist.port_list, links);
@@ -895,100 +892,118 @@ kernel_handoff(struct ctld_connection *conn)
 	}
 }
 
-int
-kernel_port_add(struct port *port)
+static bool
+ctl_create_port(const char *driver, const nvlist_t *nvl, uint32_t *ctl_port)
 {
-	struct ctl_port_entry entry;
 	struct ctl_req req;
-	struct ctl_lun_map lm;
-	struct target *targ = port->p_target;
-	struct portal_group *pg = port->p_portal_group;
 	char result_buf[NVLIST_BUFSIZE];
-	int error, i;
+	int error;
 
-	/* Create iSCSI port. */
-	if (port->p_portal_group || port->p_ioctl_port) {
-		bzero(&req, sizeof(req));
-		req.reqtype = CTL_REQ_CREATE;
-
-		if (port->p_portal_group) {
-			strlcpy(req.driver, "iscsi", sizeof(req.driver));
-			req.args_nvl = nvlist_clone(pg->pg_options);
-			nvlist_add_string(req.args_nvl, "cfiscsi_target",
-			    targ->t_name);
-			nvlist_add_string(req.args_nvl,
-			    "ctld_portal_group_name", pg->pg_name);
-			nvlist_add_stringf(req.args_nvl,
-			    "cfiscsi_portal_group_tag", "%u", pg->pg_tag);
-
-			if (targ->t_alias) {
-				nvlist_add_string(req.args_nvl,
-				    "cfiscsi_target_alias", targ->t_alias);
-			}
-		}
+	bzero(&req, sizeof(req));
+	req.reqtype = CTL_REQ_CREATE;
 
-		if (port->p_ioctl_port) {
-			strlcpy(req.driver, "ioctl", sizeof(req.driver));
-			req.args_nvl = nvlist_create(0);
-			nvlist_add_stringf(req.args_nvl, "pp", "%d",
-			    port->p_ioctl_pp);
-			nvlist_add_stringf(req.args_nvl, "vp", "%d",
-			    port->p_ioctl_vp);
-		}
+	strlcpy(req.driver, driver, sizeof(req.driver));
+	req.args = nvlist_pack(nvl, &req.args_len);
+	if (req.args == NULL) {
+		log_warn("error packing nvlist");
+		return (false);
+	}
 
-		req.args = nvlist_pack(req.args_nvl, &req.args_len);
-		if (req.args == NULL) {
-			nvlist_destroy(req.args_nvl);
-			log_warn("error packing nvlist");
-			return (1);
-		}
+	req.result = result_buf;
+	req.result_len = sizeof(result_buf);
+	error = ioctl(ctl_fd, CTL_PORT_REQ, &req);
+	free(req.args);
 
-		req.result = result_buf;
-		req.result_len = sizeof(result_buf);
-		error = ioctl(ctl_fd, CTL_PORT_REQ, &req);
-		free(req.args);
-		nvlist_destroy(req.args_nvl);
+	if (error != 0) {
+		log_warn("error issuing CTL_PORT_REQ ioctl");
+		return (false);
+	}
+	if (req.status == CTL_LUN_ERROR) {
+		log_warnx("error returned from port creation request: %s",
+		    req.error_str);
+		return (false);
+	}
+	if (req.status != CTL_LUN_OK) {
+		log_warnx("unknown port creation request status %d",
+		    req.status);
+		return (false);
+	}
 
-		if (error != 0) {
-			log_warn("error issuing CTL_PORT_REQ ioctl");
-			return (1);
-		}
-		if (req.status == CTL_LUN_ERROR) {
-			log_warnx("error returned from port creation request: %s",
-			    req.error_str);
-			return (1);
-		}
-		if (req.status != CTL_LUN_OK) {
-			log_warnx("unknown port creation request status %d",
-			    req.status);
-			return (1);
-		}
+	freebsd::nvlist_up result_nvl(nvlist_unpack(result_buf, req.result_len,
+	    0));
+	if (result_nvl == NULL) {
+		log_warnx("error unpacking result nvlist");
+		return (false);
+	}
 
-		req.result_nvl = nvlist_unpack(result_buf, req.result_len, 0);
-		if (req.result_nvl == NULL) {
-			log_warnx("error unpacking result nvlist");
-			return (1);
-		}
+	*ctl_port = nvlist_get_number(result_nvl.get(), "port_id");
+	return (true);
+}
 
-		port->p_ctl_port = nvlist_get_number(req.result_nvl, "port_id");
-		nvlist_destroy(req.result_nvl);
-	} else if (port->p_pport) {
-		port->p_ctl_port = port->p_pport->ctl_port();
-
-		if (strncmp(targ->t_name, "naa.", 4) == 0 &&
-		    strlen(targ->t_name) == 20) {
-			bzero(&entry, sizeof(entry));
-			entry.port_type = CTL_PORT_NONE;
*** 305 LINES SKIPPED ***