aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/ctld/iscsi.cc
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/ctld/iscsi.cc')
-rw-r--r--usr.sbin/ctld/iscsi.cc508
1 files changed, 508 insertions, 0 deletions
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();
+}