git: 5b0f19262952 - main - ctld: Convert struct portal to a C++ class

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

URL: https://cgit.FreeBSD.org/src/commit/?id=5b0f19262952e7d39fe9a94a0e21c739275e643b

commit 5b0f19262952e7d39fe9a94a0e21c739275e643b
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 portal to a C++ class
    
    - Convert portal_init_socket and portal_reuse_socket into class methods.
      Move the logic to handle proxy portal setup into the init_socket method
      so that some fields can remain private.
    
    - Add accessors for a few other members (most are const) so that all the
      fields can be private.
    
    - Use std::string, freebsd::addrinfo_up, and freebsd::fd_up classes to
      manage fields owned exclusively.
    
    - Add a vector of proxy portal pointers to struct conf and use the index
      into the vector as the portal ID.  This replaces an O(n^2) loop to
      find the portal for a portal ID returned by kernel_accept with a direct
      lookup.
    
    Sponsored by:   Chelsio Communications
    Pull Request:   https://github.com/freebsd/freebsd-src/pull/1794
---
 usr.sbin/ctld/ctld.cc      | 248 +++++++++++++++++++--------------------------
 usr.sbin/ctld/ctld.hh      |  41 +++++---
 usr.sbin/ctld/discovery.cc |  11 +-
 usr.sbin/ctld/kernel.cc    |   9 +-
 usr.sbin/ctld/login.cc     |  11 +-
 5 files changed, 146 insertions(+), 174 deletions(-)

diff --git a/usr.sbin/ctld/ctld.cc b/usr.sbin/ctld/ctld.cc
index 2c385baa8c5a..bb35d5ad68de 100644
--- a/usr.sbin/ctld/ctld.cc
+++ b/usr.sbin/ctld/ctld.cc
@@ -139,6 +139,23 @@ conf_delete(struct conf *conf)
 	delete conf;
 }
 
+#ifdef ICL_KERNEL_PROXY
+int
+conf::add_proxy_portal(portal *portal)
+{
+	conf_proxy_portals.push_back(portal);
+	return (conf_proxy_portals.size() - 1);
+}
+
+portal *
+conf::proxy_portal(int id)
+{
+	if (id >= conf_proxy_portals.size())
+		return (nullptr);
+	return (conf_proxy_portals[id]);
+}
+#endif
+
 bool
 auth_group::set_type(const char *str)
 {
@@ -424,31 +441,6 @@ auth_group_find(const struct conf *conf, const char *name)
 	return (it->second);
 }
 
-static struct portal *
-portal_new(struct portal_group *pg)
-{
-	struct portal *portal;
-
-	portal = reinterpret_cast<struct portal *>(calloc(1, sizeof(*portal)));
-	if (portal == NULL)
-		log_err(1, "calloc");
-	TAILQ_INIT(&portal->p_targets);
-	portal->p_portal_group = pg;
-	TAILQ_INSERT_TAIL(&pg->pg_portals, portal, p_next);
-	return (portal);
-}
-
-static void
-portal_delete(struct portal *portal)
-{
-
-	TAILQ_REMOVE(&portal->p_portal_group->pg_portals, portal, p_next);
-	if (portal->p_ai != NULL)
-		freeaddrinfo(portal->p_ai);
-	free(portal->p_listen);
-	free(portal);
-}
-
 struct portal_group *
 portal_group_new(struct conf *conf, const char *name)
 {
@@ -463,7 +455,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_portals);
 	TAILQ_INIT(&pg->pg_ports);
 	pg->pg_conf = conf;
 	pg->pg_tag = 0;		/* Assigned later in conf_apply(). */
@@ -477,15 +468,12 @@ portal_group_new(struct conf *conf, const char *name)
 void
 portal_group_delete(struct portal_group *pg)
 {
-	struct portal *portal, *tmp;
 	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);
 
-	TAILQ_FOREACH_SAFE(portal, &pg->pg_portals, p_next, tmp)
-		portal_delete(portal);
 	nvlist_destroy(pg->pg_options);
 	free(pg->pg_name);
 	free(pg->pg_offload);
@@ -557,16 +545,9 @@ parse_addr_port(const char *address, const char *def_port)
 bool
 portal_group_add_portal(struct portal_group *pg, const char *value, bool iser)
 {
-	struct portal *portal;
-
-	portal = portal_new(pg);
-	portal->p_listen = checked_strdup(value);
-	portal->p_iser = iser;
-
-	freebsd::addrinfo_up ai = parse_addr_port(portal->p_listen, "3260");
+	freebsd::addrinfo_up ai = parse_addr_port(value, "3260");
 	if (!ai) {
-		log_warnx("invalid listen address %s", portal->p_listen);
-		portal_delete(portal);
+		log_warnx("invalid listen address %s", value);
 		return (false);
 	}
 
@@ -575,7 +556,8 @@ portal_group_add_portal(struct portal_group *pg, const char *value, bool iser)
 	 *	those into multiple portals.
 	 */
 
-	portal->p_ai = ai.release();
+	pg->pg_portals.emplace_back(std::make_unique<portal>(pg, value, iser,
+	    std::move(ai)));
 	return (true);
 }
 
@@ -642,7 +624,6 @@ isns_do_register(struct isns *isns, int s, const char *hostname)
 {
 	struct conf *conf = isns->i_conf;
 	struct target *target;
-	struct portal *portal;
 	struct portal_group *pg;
 	struct port *port;
 	uint32_t error;
@@ -656,9 +637,9 @@ isns_do_register(struct isns *isns, int s, const char *hostname)
 	TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) {
 		if (pg->pg_unassigned)
 			continue;
-		TAILQ_FOREACH(portal, &pg->pg_portals, p_next) {
-			req.add_addr(16, portal->p_ai);
-			req.add_port(17, portal->p_ai);
+		for (const portal_up &portal : pg->pg_portals) {
+			req.add_addr(16, portal->ai());
+			req.add_port(17, portal->ai());
 		}
 	}
 	TAILQ_FOREACH(target, &conf->conf_targets, t_next) {
@@ -670,9 +651,9 @@ isns_do_register(struct isns *isns, int s, const char *hostname)
 			if ((pg = port->p_portal_group) == NULL)
 				continue;
 			req.add_32(51, pg->pg_tag);
-			TAILQ_FOREACH(portal, &pg->pg_portals, p_next) {
-				req.add_addr(49, portal->p_ai);
-				req.add_port(50, portal->p_ai);
+			for (const portal_up &portal : pg->pg_portals) {
+				req.add_addr(49, portal->ai());
+				req.add_port(50, portal->ai());
 			}
 		}
 	}
@@ -1013,7 +994,7 @@ port_is_dummy(struct port *port)
 	if (port->p_portal_group) {
 		if (port->p_portal_group->pg_foreign)
 			return (true);
-		if (TAILQ_EMPTY(&port->p_portal_group->pg_portals))
+		if (port->p_portal_group->pg_portals.empty())
 			return (true);
 	}
 	return (false);
@@ -1363,124 +1344,126 @@ conf_verify(struct conf *conf)
 	return (true);
 }
 
-static bool
-portal_reuse_socket(struct portal *oldp, struct portal *newp)
+bool
+portal::reuse_socket(struct portal &oldp)
 {
 	struct kevent kev;
 
-	if (strcmp(newp->p_listen, oldp->p_listen) != 0)
+	if (p_listen != oldp.p_listen)
 		return (false);
 
-	if (oldp->p_socket <= 0)
+	if (!oldp.p_socket)
 		return (false);
 
-	EV_SET(&kev, oldp->p_socket, EVFILT_READ, EV_ADD, 0, 0, newp);
+	EV_SET(&kev, oldp.p_socket, EVFILT_READ, EV_ADD, 0, 0, this);
 	if (kevent(kqfd, &kev, 1, NULL, 0, NULL) == -1)
 		return (false);
 
-	newp->p_socket = oldp->p_socket;
-	oldp->p_socket = 0;
+	p_socket = std::move(oldp.p_socket);
 	return (true);
 }
 
-static bool
-portal_init_socket(struct portal *p)
+bool
+portal::init_socket()
 {
-	struct portal_group *pg = p->p_portal_group;
+	struct portal_group *pg = portal_group();
 	struct kevent kev;
+	freebsd::fd_up s;
 	int error, sockbuf;
 	int one = 1;
 
-	log_debugx("listening on %s, portal-group \"%s\"",
-	    p->p_listen, pg->pg_name);
-	p->p_socket = socket(p->p_ai->ai_family, p->p_ai->ai_socktype,
-	    p->p_ai->ai_protocol);
-	if (p->p_socket < 0) {
-		log_warn("socket(2) failed for %s",
-		    p->p_listen);
+#ifdef ICL_KERNEL_PROXY
+	if (proxy_mode) {
+		int id = pg->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);
+		return (true);
+	}
+#endif
+	assert(proxy_mode == false);
+	assert(p_iser == false);
+
+	log_debugx("listening on %s, portal-group \"%s\"", listen(),
+	    pg->pg_name);
+	s = ::socket(p_ai->ai_family, p_ai->ai_socktype, p_ai->ai_protocol);
+	if (!s) {
+		log_warn("socket(2) failed for %s", listen());
 		return (false);
 	}
 
 	sockbuf = SOCKBUF_SIZE;
-	if (setsockopt(p->p_socket, SOL_SOCKET, SO_RCVBUF, &sockbuf,
+	if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &sockbuf,
 	    sizeof(sockbuf)) == -1)
-		log_warn("setsockopt(SO_RCVBUF) failed for %s",
-		    p->p_listen);
+		log_warn("setsockopt(SO_RCVBUF) failed for %s", listen());
 	sockbuf = SOCKBUF_SIZE;
-	if (setsockopt(p->p_socket, SOL_SOCKET, SO_SNDBUF, &sockbuf,
+	if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sockbuf,
 	    sizeof(sockbuf)) == -1)
-		log_warn("setsockopt(SO_SNDBUF) failed for %s", p->p_listen);
-	if (setsockopt(p->p_socket, SOL_SOCKET, SO_NO_DDP, &one,
+		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", p->p_listen);
-	error = setsockopt(p->p_socket, SOL_SOCKET, SO_REUSEADDR, &one,
+		log_warn("setsockopt(SO_NO_DDP) failed for %s", listen());
+	error = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one,
 	    sizeof(one));
 	if (error != 0) {
-		log_warn("setsockopt(SO_REUSEADDR) failed for %s", p->p_listen);
-		close(p->p_socket);
-		p->p_socket = 0;
+		log_warn("setsockopt(SO_REUSEADDR) failed for %s", listen());
 		return (false);
 	}
 
 	if (pg->pg_dscp != -1) {
 		/* Only allow the 6-bit DSCP field to be modified */
 		int tos = pg->pg_dscp << 2;
-		switch (p->p_ai->ai_family) {
+		switch (p_ai->ai_family) {
 		case AF_INET:
-			if (setsockopt(p->p_socket, IPPROTO_IP, IP_TOS,
+			if (setsockopt(s, IPPROTO_IP, IP_TOS,
 			    &tos, sizeof(tos)) == -1)
 				log_warn("setsockopt(IP_TOS) failed for %s",
-				    p->p_listen);
+				    listen());
 			break;
 		case AF_INET6:
-			if (setsockopt(p->p_socket, IPPROTO_IPV6, IPV6_TCLASS,
+			if (setsockopt(s, IPPROTO_IPV6, IPV6_TCLASS,
 			    &tos, sizeof(tos)) == -1)
 				log_warn("setsockopt(IPV6_TCLASS) failed for %s",
-				    p->p_listen);
+				    listen());
 			break;
 		}
 	}
 	if (pg->pg_pcp != -1) {
 		int pcp = pg->pg_pcp;
-		switch (p->p_ai->ai_family) {
+		switch (p_ai->ai_family) {
 		case AF_INET:
-			if (setsockopt(p->p_socket, IPPROTO_IP, IP_VLAN_PCP,
+			if (setsockopt(s, IPPROTO_IP, IP_VLAN_PCP,
 			    &pcp, sizeof(pcp)) == -1)
 				log_warn("setsockopt(IP_VLAN_PCP) failed for %s",
-				    p->p_listen);
+				    listen());
 			break;
 		case AF_INET6:
-			if (setsockopt(p->p_socket, IPPROTO_IPV6, IPV6_VLAN_PCP,
+			if (setsockopt(s, IPPROTO_IPV6, IPV6_VLAN_PCP,
 			    &pcp, sizeof(pcp)) == -1)
 				log_warn("setsockopt(IPV6_VLAN_PCP) failed for %s",
-				    p->p_listen);
+				    listen());
 			break;
 		}
 	}
 
-	error = bind(p->p_socket, p->p_ai->ai_addr,
-	    p->p_ai->ai_addrlen);
+	error = bind(s, p_ai->ai_addr, p_ai->ai_addrlen);
 	if (error != 0) {
-		log_warn("bind(2) failed for %s", p->p_listen);
-		close(p->p_socket);
-		p->p_socket = 0;
+		log_warn("bind(2) failed for %s", listen());
 		return (false);
 	}
-	error = listen(p->p_socket, -1);
+	error = ::listen(s, -1);
 	if (error != 0) {
-		log_warn("listen(2) failed for %s", p->p_listen);
-		close(p->p_socket);
-		p->p_socket = 0;
+		log_warn("listen(2) failed for %s", listen());
 		return (false);
 	}
-	EV_SET(&kev, p->p_socket, EVFILT_READ, EV_ADD, 0, 0, p);
+	EV_SET(&kev, s, EVFILT_READ, EV_ADD, 0, 0, this);
 	error = kevent(kqfd, &kev, 1, NULL, 0, NULL);
 	if (error == -1) {
-		log_warn("kevent(2) failed to register for %s", p->p_listen);
-		close(p->p_socket);
-		p->p_socket = 0;
+		log_warn("kevent(2) failed to register for %s", listen());
 		return (false);
 	}
+	p_socket = std::move(s);
 	return (true);
 }
 
@@ -1489,7 +1472,6 @@ conf_apply(struct conf *oldconf, struct conf *newconf)
 {
 	struct lun *oldlun, *newlun, *tmplun;
 	struct portal_group *oldpg, *newpg;
-	struct portal *oldp, *newp;
 	struct port *oldport, *newport, *tmpport;
 	struct isns *oldns, *newns;
 	int changed, cumulated_error = 0, error;
@@ -1716,7 +1698,7 @@ conf_apply(struct conf *oldconf, struct conf *newconf)
 			    newpg->pg_name);
 			continue;
 		}
-		TAILQ_FOREACH(newp, &newpg->pg_portals, p_next) {
+		for (portal_up &newp : newpg->pg_portals) {
 			/*
 			 * Try to find already open portal and reuse
 			 * the listening socket.  We don't care about
@@ -1725,39 +1707,18 @@ conf_apply(struct conf *oldconf, struct conf *newconf)
 			 */
 			TAILQ_FOREACH(oldpg, &oldconf->conf_portal_groups,
 			    pg_next) {
-				TAILQ_FOREACH(oldp, &oldpg->pg_portals,
-				    p_next) {
-					if (portal_reuse_socket(oldp, newp))
+				for (portal_up &oldp : oldpg->pg_portals) {
+					if (newp->reuse_socket(*oldp))
 						goto reused;
 				}
 			}
-		reused:
-			if (newp->p_socket > 0) {
-				/*
-				 * We're done with this portal.
-				 */
-				continue;
-			}
 
-#ifdef ICL_KERNEL_PROXY
-			if (proxy_mode) {
-				newpg->pg_conf->conf_portal_id++;
-				newp->p_id = newpg->pg_conf->conf_portal_id;
-				log_debugx("listening on %s, portal-group "
-				    "\"%s\", portal id %d, using ICL proxy",
-				    newp->p_listen, newpg->pg_name, newp->p_id);
-				kernel_listen(newp->p_ai, newp->p_iser,
-				    newp->p_id);
-				continue;
-			}
-#endif
-			assert(proxy_mode == false);
-			assert(newp->p_iser == false);
-
-			if (!portal_init_socket(newp)) {
+			if (!newp->init_socket()) {
 				cumulated_error++;
 				continue;
 			}
+		reused:
+			;
 		}
 	}
 
@@ -1765,13 +1726,12 @@ conf_apply(struct conf *oldconf, struct conf *newconf)
 	 * Go through the no longer used sockets, closing them.
 	 */
 	TAILQ_FOREACH(oldpg, &oldconf->conf_portal_groups, pg_next) {
-		TAILQ_FOREACH(oldp, &oldpg->pg_portals, p_next) {
-			if (oldp->p_socket <= 0)
+		for (portal_up &oldp : oldpg->pg_portals) {
+			if (oldp->socket() < 0)
 				continue;
 			log_debugx("closing socket for %s, portal-group \"%s\"",
-			    oldp->p_listen, oldpg->pg_name);
-			close(oldp->p_socket);
-			oldp->p_socket = 0;
+			    oldp->listen(), oldpg->pg_name);
+			oldp->close();
 		}
 	}
 
@@ -1903,12 +1863,14 @@ handle_connection(struct portal *portal, int fd,
     const struct sockaddr *client_sa, bool dont_fork)
 {
 	struct ctld_connection *conn;
+	struct portal_group *pg;
 	int error;
 	pid_t pid;
 	char host[NI_MAXHOST + 1];
 	struct conf *conf;
 
-	conf = portal->p_portal_group->pg_conf;
+	pg = portal->portal_group();
+	conf = pg->pg_conf;
 
 	if (dont_fork) {
 		log_debugx("incoming connection; not forking due to -d flag");
@@ -1941,7 +1903,7 @@ handle_connection(struct portal *portal, int fd,
 		log_errx(1, "getnameinfo: %s", gai_strerror(error));
 
 	log_debugx("accepted connection from %s; portal group \"%s\"",
-	    host, portal->p_portal_group->pg_name);
+	    host, pg->pg_name);
 	log_set_peer_addr(host);
 	setproctitle("%s", host);
 
@@ -1986,18 +1948,12 @@ main_loop(bool dont_fork)
 
 			log_debugx("incoming connection, id %d, portal id %d",
 			    connection_id, portal_id);
-			TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) {
-				TAILQ_FOREACH(portal, &pg->pg_portals, p_next) {
-					if (portal->p_id == portal_id) {
-						goto found;
-					}
-				}
-			}
-
-			log_errx(1, "kernel returned invalid portal_id %d",
-			    portal_id);
+			portal = conf->proxy_portal(portal_id);
+			if (portal == nullptr)
+				log_errx(1,
+				    "kernel returned invalid portal_id %d",
+				    portal_id);
 
-found:
 			handle_connection(portal, connection_id,
 			    (struct sockaddr *)&client_sa, dont_fork);
 		} else {
@@ -2014,10 +1970,10 @@ found:
 			switch (kev.filter) {
 			case EVFILT_READ:
 				portal = reinterpret_cast<struct portal *>(kev.udata);
-				assert(portal->p_socket == (int)kev.ident);
+				assert(portal->socket() == (int)kev.ident);
 
 				client_salen = sizeof(client_sa);
-				client_fd = accept(portal->p_socket,
+				client_fd = accept(portal->socket(),
 				    (struct sockaddr *)&client_sa,
 				    &client_salen);
 				if (client_fd < 0) {
diff --git a/usr.sbin/ctld/ctld.hh b/usr.sbin/ctld/ctld.hh
index 585e5d24663d..ea7f41f707b9 100644
--- a/usr.sbin/ctld/ctld.hh
+++ b/usr.sbin/ctld/ctld.hh
@@ -47,6 +47,7 @@
 #include <string_view>
 #include <unordered_map>
 #include <unordered_set>
+#include <libutil++.hh>
 
 #define	DEFAULT_CONFIG_PATH		"/etc/ctl.conf"
 #define	DEFAULT_PIDFILE			"/var/run/ctld.pid"
@@ -126,19 +127,31 @@ private:
 using auth_group_sp = std::shared_ptr<auth_group>;
 
 struct portal {
-	TAILQ_ENTRY(portal)		p_next;
+	portal(struct portal_group *pg, std::string_view listen, bool iser,
+	    freebsd::addrinfo_up ai) :
+		p_portal_group(pg), p_listen(listen), p_ai(std::move(ai)),
+		p_iser(iser) {}
+
+	bool reuse_socket(portal &oldp);
+	bool init_socket();
+
+	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(); }
+	int socket() const { return p_socket; }
+	void close() { p_socket.reset(); }
+
+private:
 	struct portal_group		*p_portal_group;
+	std::string			p_listen;
+	freebsd::addrinfo_up		p_ai;
 	bool				p_iser;
-	char				*p_listen;
-	struct addrinfo			*p_ai;
-#ifdef ICL_KERNEL_PROXY
-	int				p_id;
-#endif
 
-	TAILQ_HEAD(, target)		p_targets;
-	int				p_socket;
+	freebsd::fd_up			p_socket;
 };
 
+using portal_up = std::unique_ptr<portal>;
+
 #define	PG_FILTER_UNKNOWN		0
 #define	PG_FILTER_NONE			1
 #define	PG_FILTER_PORTAL		2
@@ -154,7 +167,7 @@ struct portal_group {
 	int				pg_discovery_filter = PG_FILTER_UNKNOWN;
 	bool				pg_foreign = false;
 	bool				pg_unassigned = false;
-	TAILQ_HEAD(, portal)		pg_portals;
+	std::list<portal_up>	        pg_portals;
 	TAILQ_HEAD(, port)		pg_ports;
 	char				*pg_offload = nullptr;
 	char				*pg_redirection = nullptr;
@@ -244,14 +257,18 @@ struct conf {
 	int				conf_timeout;
 	int				conf_maxproc;
 
-#ifdef ICL_KERNEL_PROXY
-	int				conf_portal_id = 0;
-#endif
 	struct pidfh			*conf_pidfh = nullptr;
 
 	bool				conf_default_pg_defined = false;
 	bool				conf_default_ag_defined = false;
 	bool				conf_kernel_port_on = false;
+
+#ifdef ICL_KERNEL_PROXY
+	int add_proxy_portal(portal *);
+	portal *proxy_portal(int);
+private:
+	std::vector<portal *>		conf_proxy_portals;
+#endif
 };
 
 /* Physical ports exposed by the kernel */
diff --git a/usr.sbin/ctld/discovery.cc b/usr.sbin/ctld/discovery.cc
index 5ef8a7e7027b..59bdadb29eb2 100644
--- a/usr.sbin/ctld/discovery.cc
+++ b/usr.sbin/ctld/discovery.cc
@@ -102,18 +102,17 @@ static void
 discovery_add_target(struct keys *response_keys, const struct target *targ)
 {
 	struct port *port;
-	struct portal *portal;
 	char *buf;
 	char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
-	struct addrinfo *ai;
+	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)
 		continue;
-	    TAILQ_FOREACH(portal, &port->p_portal_group->pg_portals, p_next) {
-		ai = portal->p_ai;
+	    for (portal_up &portal : port->p_portal_group->pg_portals) {
+		ai = portal->ai();
 		ret = getnameinfo(ai->ai_addr, ai->ai_addrlen,
 		    hbuf, sizeof(hbuf), sbuf, sizeof(sbuf),
 		    NI_NUMERICHOST | NI_NUMERICSERV);
@@ -159,7 +158,7 @@ discovery_target_filtered_out(const struct ctld_connection *conn,
 	ag = port->p_auth_group.get();
 	if (ag == nullptr)
 		ag = targ->t_auth_group.get();
-	pg = conn->conn_portal->p_portal_group;
+	pg = conn->conn_portal->portal_group();
 
 	assert(pg->pg_discovery_filter != PG_FILTER_UNKNOWN);
 
@@ -217,7 +216,7 @@ discovery(struct ctld_connection *conn)
 	const struct portal_group *pg;
 	const char *send_targets;
 
-	pg = conn->conn_portal->p_portal_group;
+	pg = conn->conn_portal->portal_group();
 
 	log_debugx("beginning discovery session; waiting for TextRequest PDU");
 	request_keys = text_read_request(&conn->conn, &request);
diff --git a/usr.sbin/ctld/kernel.cc b/usr.sbin/ctld/kernel.cc
index fbe6d9a2f22a..5a9a9f3c991f 100644
--- a/usr.sbin/ctld/kernel.cc
+++ b/usr.sbin/ctld/kernel.cc
@@ -841,6 +841,7 @@ kernel_lun_remove(struct lun *lun)
 void
 kernel_handoff(struct ctld_connection *conn)
 {
+	struct portal_group *pg = conn->conn_portal->portal_group();
 	struct ctl_iscsi req;
 
 	bzero(&req, sizeof(req));
@@ -858,9 +859,8 @@ kernel_handoff(struct ctld_connection *conn)
 	    sizeof(req.data.handoff.initiator_isid));
 	strlcpy(req.data.handoff.target_name,
 	    conn->conn_target->t_name, sizeof(req.data.handoff.target_name));
-	if (conn->conn_portal->p_portal_group->pg_offload != NULL) {
-		strlcpy(req.data.handoff.offload,
-		    conn->conn_portal->p_portal_group->pg_offload,
+	if (pg->pg_offload != NULL) {
+		strlcpy(req.data.handoff.offload, pg->pg_offload,
 		    sizeof(req.data.handoff.offload));
 	}
 #ifdef ICL_KERNEL_PROXY
@@ -871,8 +871,7 @@ kernel_handoff(struct ctld_connection *conn)
 #else
 	req.data.handoff.socket = conn->conn.conn_socket;
 #endif
-	req.data.handoff.portal_group_tag =
-	    conn->conn_portal->p_portal_group->pg_tag;
+	req.data.handoff.portal_group_tag = pg->pg_tag;
 	if (conn->conn.conn_header_digest == CONN_DIGEST_CRC32C)
 		req.data.handoff.header_digest = CTL_ISCSI_DIGEST_CRC32C;
 	if (conn->conn.conn_data_digest == CONN_DIGEST_CRC32C)
diff --git a/usr.sbin/ctld/login.cc b/usr.sbin/ctld/login.cc
index 221a7af56cc6..416c867df08d 100644
--- a/usr.sbin/ctld/login.cc
+++ b/usr.sbin/ctld/login.cc
@@ -703,7 +703,7 @@ login_portal_redirect(struct ctld_connection *conn, struct pdu *request)
 {
 	const struct portal_group *pg;
 
-	pg = conn->conn_portal->p_portal_group;
+	pg = conn->conn_portal->portal_group();
 	if (pg->pg_redirection == NULL)
 		return (false);
 
@@ -719,7 +719,7 @@ login_target_redirect(struct ctld_connection *conn, struct pdu *request)
 {
 	const char *target_address;
 
-	assert(conn->conn_portal->p_portal_group->pg_redirection == NULL);
+	assert(conn->conn_portal->portal_group()->pg_redirection == NULL);
 
 	if (conn->conn_target == NULL)
 		return (false);
@@ -738,6 +738,7 @@ login_target_redirect(struct ctld_connection *conn, struct pdu *request)
 static void
 login_negotiate(struct ctld_connection *conn, struct pdu *request)
 {
+	struct portal_group *pg = conn->conn_portal->portal_group();
 	struct pdu *response;
 	struct iscsi_bhs_login_response *bhslr2;
 	struct keys *request_keys, *response_keys;
@@ -754,7 +755,7 @@ login_negotiate(struct ctld_connection *conn, struct pdu *request)
 		conn->conn_max_send_data_segment_limit = (1 << 24) - 1;
 		conn->conn_max_burst_limit = (1 << 24) - 1;
 		conn->conn_first_burst_limit = (1 << 24) - 1;
-		kernel_limits(conn->conn_portal->p_portal_group->pg_offload,
+		kernel_limits(pg->pg_offload,
 		    conn->conn.conn_socket,
 		    &conn->conn_max_recv_data_segment_limit,
 		    &conn->conn_max_send_data_segment_limit,
@@ -826,7 +827,7 @@ login_negotiate(struct ctld_connection *conn, struct pdu *request)
 			keys_add(response_keys,
 			    "TargetAlias", conn->conn_target->t_alias);
 		keys_add_int(response_keys, "TargetPortalGroupTag",
-		    conn->conn_portal->p_portal_group->pg_tag);
+		    pg->pg_tag);
 	}
 
 	for (i = 0; i < KEYS_MAX; i++) {
@@ -916,7 +917,7 @@ login(struct ctld_connection *conn)
 		log_errx(1, "received Login PDU with non-zero TSIH");
 	}
 
-	pg = conn->conn_portal->p_portal_group;
+	pg = conn->conn_portal->portal_group();
 
 	memcpy(conn->conn_initiator_isid, bhslr->bhslr_isid,
 	    sizeof(conn->conn_initiator_isid));