xref: /freebsd/usr.sbin/ctld/iscsi.cc (revision 40484d3117d9520de3755d5c91ddb26ed7ce6bcb)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2003, 2004 Silicon Graphics International Corp.
5  * Copyright (c) 1997-2007 Kenneth D. Merry
6  * Copyright (c) 2012 The FreeBSD Foundation
7  * Copyright (c) 2017 Jakub Wojciech Klama <jceel@FreeBSD.org>
8  * All rights reserved.
9  * Copyright (c) 2025 Chelsio Communications, Inc.
10  *
11  * Portions of this software were developed by Edward Tomasz Napierala
12  * under sponsorship from the FreeBSD Foundation.
13  *
14  * Redistribution and use in source and binary forms, with or without
15  * modification, are permitted provided that the following conditions
16  * are met:
17  * 1. Redistributions of source code must retain the above copyright
18  *    notice, this list of conditions, and the following disclaimer,
19  *    without modification.
20  * 2. Redistributions in binary form must reproduce at minimum a disclaimer
21  *    substantially similar to the "NO WARRANTY" disclaimer below
22  *    ("Disclaimer") and any redistribution must be conditioned upon
23  *    including a substantially similar Disclaimer requirement for further
24  *    binary redistribution.
25  *
26  * NO WARRANTY
27  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
30  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31  * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
35  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
36  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37  * POSSIBILITY OF SUCH DAMAGES.
38  *
39  */
40 
41 #include <sys/param.h>
42 #include <sys/linker.h>
43 #include <sys/module.h>
44 #include <sys/time.h>
45 #include <assert.h>
46 #include <libiscsiutil.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <cam/ctl/ctl.h>
50 #include <cam/ctl/ctl_io.h>
51 #include <cam/ctl/ctl_ioctl.h>
52 
53 #include "ctld.hh"
54 #include "iscsi.hh"
55 
56 #define	SOCKBUF_SIZE			1048576
57 
58 struct iscsi_portal final : public portal {
iscsi_portaliscsi_portal59 	iscsi_portal(struct portal_group *pg, const char *listen,
60 	    portal_protocol protocol, freebsd::addrinfo_up ai) :
61 		portal(pg, listen, protocol, std::move(ai)) {}
62 
63 	bool init_socket_options(int s) override;
64 	void handle_connection(freebsd::fd_up fd, const char *host,
65 	    const struct sockaddr *client_sa) override;
66 };
67 
68 struct iscsi_portal_group final : public portal_group {
iscsi_portal_groupiscsi_portal_group69 	iscsi_portal_group(struct conf *conf, std::string_view name) :
70 		portal_group(conf, name) {}
71 
keywordiscsi_portal_group72 	const char *keyword() const override
73 	{ return "portal-group"; }
74 
75 	void allocate_tag() override;
76 	bool add_portal(const char *value, portal_protocol protocol)
77 	    override;
78 	void add_default_portals() override;
79 	bool set_filter(const char *str) override;
80 
81 	virtual port_up create_port(struct target *target, auth_group_sp ag)
82 	    override;
83 	virtual port_up create_port(struct target *target, uint32_t ctl_port)
84 	    override;
85 private:
86 	static uint16_t last_portal_group_tag;
87 };
88 
89 struct iscsi_port final : public portal_group_port {
iscsi_portiscsi_port90 	iscsi_port(struct target *target, struct portal_group *pg,
91 	    auth_group_sp ag) :
92 		portal_group_port(target, pg, ag) {}
iscsi_portiscsi_port93 	iscsi_port(struct target *target, struct portal_group *pg,
94 	    uint32_t ctl_port) :
95 		portal_group_port(target, pg, ctl_port) {}
96 
97 	bool kernel_create_port() override;
98 	bool kernel_remove_port() override;
99 
100 private:
101 	static bool module_loaded;
102 	static void load_kernel_module();
103 };
104 
105 struct iscsi_target final : public target {
iscsi_targetiscsi_target106 	iscsi_target(struct conf *conf, std::string_view name) :
107 		target(conf, "target", name) {}
108 
109 	bool add_initiator_name(std::string_view name) override;
110 	bool add_initiator_portal(const char *addr) override;
111 	bool add_lun(u_int id, const char *lun_name) override;
112 	bool add_portal_group(const char *pg_name, const char *ag_name)
113 	    override;
114 	struct lun *start_lun(u_int id) override;
115 
116 protected:
117 	struct portal_group *default_portal_group() override;
118 };
119 
120 #ifdef ICL_KERNEL_PROXY
121 static void	pdu_receive_proxy(struct pdu *pdu);
122 static void	pdu_send_proxy(struct pdu *pdu);
123 #endif /* ICL_KERNEL_PROXY */
124 static void	pdu_fail(const struct connection *conn, const char *reason);
125 
126 uint16_t iscsi_portal_group::last_portal_group_tag = 0xff;
127 bool	iscsi_port::module_loaded = false;
128 
129 static struct connection_ops conn_ops = {
130 	.timed_out = timed_out,
131 #ifdef ICL_KERNEL_PROXY
132 	.pdu_receive_proxy = pdu_receive_proxy,
133 	.pdu_send_proxy = pdu_send_proxy,
134 #else
135 	.pdu_receive_proxy = nullptr,
136 	.pdu_send_proxy = nullptr,
137 #endif
138 	.fail = pdu_fail,
139 };
140 
141 portal_group_up
iscsi_make_portal_group(struct conf * conf,std::string_view name)142 iscsi_make_portal_group(struct conf *conf, std::string_view name)
143 {
144 	return std::make_unique<iscsi_portal_group>(conf, name);
145 }
146 
147 target_up
iscsi_make_target(struct conf * conf,std::string_view name)148 iscsi_make_target(struct conf *conf, std::string_view name)
149 {
150 	return std::make_unique<iscsi_target>(conf, name);
151 }
152 
153 void
allocate_tag()154 iscsi_portal_group::allocate_tag()
155 {
156 	set_tag(++last_portal_group_tag);
157 }
158 
159 bool
add_portal(const char * value,portal_protocol protocol)160 iscsi_portal_group::add_portal(const char *value, portal_protocol protocol)
161 {
162 	switch (protocol) {
163 	case portal_protocol::ISCSI:
164 	case portal_protocol::ISER:
165 		break;
166 	default:
167 		log_warnx("unsupported portal protocol for %s", value);
168 		return (false);
169 	}
170 
171 	freebsd::addrinfo_up ai = parse_addr_port(value, "3260");
172 	if (!ai) {
173 		log_warnx("invalid listen address %s", value);
174 		return (false);
175 	}
176 
177 	/*
178 	 * XXX: getaddrinfo(3) may return multiple addresses; we should turn
179 	 *	those into multiple portals.
180 	 */
181 
182 	pg_portals.emplace_back(std::make_unique<iscsi_portal>(this, value,
183 	    protocol, std::move(ai)));
184 	return (true);
185 }
186 
187 void
add_default_portals()188 iscsi_portal_group::add_default_portals()
189 {
190 	add_portal("0.0.0.0", portal_protocol::ISCSI);
191 	add_portal("[::]", portal_protocol::ISCSI);
192 }
193 
194 bool
set_filter(const char * str)195 iscsi_portal_group::set_filter(const char *str)
196 {
197 	enum discovery_filter filter;
198 
199 	if (strcmp(str, "none") == 0) {
200 		filter = discovery_filter::NONE;
201 	} else if (strcmp(str, "portal") == 0) {
202 		filter = discovery_filter::PORTAL;
203 	} else if (strcmp(str, "portal-name") == 0) {
204 		filter = discovery_filter::PORTAL_NAME;
205 	} else if (strcmp(str, "portal-name-auth") == 0) {
206 		filter = discovery_filter::PORTAL_NAME_AUTH;
207 	} else {
208 		log_warnx("invalid discovery-filter \"%s\" for portal-group "
209 		    "\"%s\"; valid values are \"none\", \"portal\", "
210 		    "\"portal-name\", and \"portal-name-auth\"",
211 		    str, name());
212 		return (false);
213 	}
214 
215 	if (pg_discovery_filter != discovery_filter::UNKNOWN &&
216 	    pg_discovery_filter != filter) {
217 		log_warnx("cannot set discovery-filter to \"%s\" for "
218 		    "portal-group \"%s\"; already has a different "
219 		    "value", str, name());
220 		return (false);
221 	}
222 
223 	pg_discovery_filter = filter;
224 	return (true);
225 }
226 
227 port_up
create_port(struct target * target,auth_group_sp ag)228 iscsi_portal_group::create_port(struct target *target, auth_group_sp ag)
229 {
230 	return std::make_unique<iscsi_port>(target, this, ag);
231 }
232 
233 port_up
create_port(struct target * target,uint32_t ctl_port)234 iscsi_portal_group::create_port(struct target *target, uint32_t ctl_port)
235 {
236 	return std::make_unique<iscsi_port>(target, this, ctl_port);
237 }
238 
239 void
load_kernel_module()240 iscsi_port::load_kernel_module()
241 {
242 	int saved_errno;
243 
244 	if (module_loaded)
245 		return;
246 
247 	saved_errno = errno;
248 	if (modfind("cfiscsi") == -1 && kldload("cfiscsi") == -1)
249 		log_warn("couldn't load cfiscsi");
250 	errno = saved_errno;
251 	module_loaded = true;
252 }
253 
254 bool
kernel_create_port()255 iscsi_port::kernel_create_port()
256 {
257 	struct portal_group *pg = p_portal_group;
258 	struct target *targ = p_target;
259 
260 	load_kernel_module();
261 
262 	freebsd::nvlist_up nvl = pg->options();
263 	nvlist_add_string(nvl.get(), "cfiscsi_target", targ->name());
264 	nvlist_add_string(nvl.get(), "ctld_portal_group_name", pg->name());
265 	nvlist_add_stringf(nvl.get(), "cfiscsi_portal_group_tag", "%u",
266 	    pg->tag());
267 
268 	if (targ->has_alias()) {
269 		nvlist_add_string(nvl.get(), "cfiscsi_target_alias",
270 		    targ->alias());
271 	}
272 
273 	return (ctl_create_port("iscsi", nvl.get(), &p_ctl_port));
274 }
275 
276 bool
kernel_remove_port()277 iscsi_port::kernel_remove_port()
278 {
279 	freebsd::nvlist_up nvl(nvlist_create(0));
280 	nvlist_add_string(nvl.get(), "cfiscsi_target", p_target->name());
281 	nvlist_add_stringf(nvl.get(), "cfiscsi_portal_group_tag", "%u",
282 	    p_portal_group->tag());
283 
284 	return (ctl_remove_port("iscsi", nvl.get()));
285 }
286 
287 bool
init_socket_options(int s)288 iscsi_portal::init_socket_options(int s)
289 {
290 	int sockbuf;
291 
292 	sockbuf = SOCKBUF_SIZE;
293 	if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &sockbuf,
294 	    sizeof(sockbuf)) == -1) {
295 		log_warn("setsockopt(SO_RCVBUF) failed for %s", listen());
296 		return (false);
297 	}
298 	sockbuf = SOCKBUF_SIZE;
299 	if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sockbuf,
300 	    sizeof(sockbuf)) == -1) {
301 		log_warn("setsockopt(SO_SNDBUF) failed for %s", listen());
302 		return (false);
303 	}
304 	return (true);
305 }
306 
307 bool
add_initiator_name(std::string_view name)308 iscsi_target::add_initiator_name(std::string_view name)
309 {
310 	if (!use_private_auth("initiator-name"))
311 		return (false);
312 	return (t_auth_group->add_initiator_name(name));
313 }
314 
315 bool
add_initiator_portal(const char * addr)316 iscsi_target::add_initiator_portal(const char *addr)
317 {
318 	if (!use_private_auth("initiator-portal"))
319 		return (false);
320 	return (t_auth_group->add_initiator_portal(addr));
321 }
322 
323 bool
add_lun(u_int id,const char * lun_name)324 iscsi_target::add_lun(u_int id, const char *lun_name)
325 {
326 	std::string lun_label = "LUN " + std::to_string(id);
327 	return target::add_lun(id, lun_label.c_str(), lun_name);
328 }
329 
330 bool
add_portal_group(const char * pg_name,const char * ag_name)331 iscsi_target::add_portal_group(const char *pg_name, const char *ag_name)
332 {
333 	struct portal_group *pg;
334 	auth_group_sp ag;
335 
336 	pg = t_conf->find_portal_group(pg_name);
337 	if (pg == NULL) {
338 		log_warnx("unknown portal-group \"%s\" for %s", pg_name,
339 		    label());
340 		return (false);
341 	}
342 
343 	if (ag_name != NULL) {
344 		ag = t_conf->find_auth_group(ag_name);
345 		if (ag == NULL) {
346 			log_warnx("unknown auth-group \"%s\" for %s", ag_name,
347 			    label());
348 			return (false);
349 		}
350 	}
351 
352 	if (!t_conf->add_port(this, pg, std::move(ag))) {
353 		log_warnx("can't link portal-group \"%s\" to %s", pg_name,
354 		    label());
355 		return (false);
356 	}
357 	return (true);
358 }
359 
360 struct lun *
start_lun(u_int id)361 iscsi_target::start_lun(u_int id)
362 {
363 	std::string lun_label = "LUN " + std::to_string(id);
364 	std::string lun_name = freebsd::stringf("%s,lun,%u", name(), id);
365 	return target::start_lun(id, lun_label.c_str(), lun_name.c_str());
366 }
367 
368 struct portal_group *
default_portal_group()369 iscsi_target::default_portal_group()
370 {
371 	return t_conf->find_portal_group("default");
372 }
373 
374 #ifdef ICL_KERNEL_PROXY
375 
376 static void
pdu_receive_proxy(struct pdu * pdu)377 pdu_receive_proxy(struct pdu *pdu)
378 {
379 	struct connection *conn;
380 	size_t len;
381 
382 	assert(proxy_mode);
383 	conn = pdu->pdu_connection;
384 
385 	kernel_receive(pdu);
386 
387 	len = pdu_ahs_length(pdu);
388 	if (len > 0)
389 		log_errx(1, "protocol error: non-empty AHS");
390 
391 	len = pdu_data_segment_length(pdu);
392 	assert(len <= (size_t)conn->conn_max_recv_data_segment_length);
393 	pdu->pdu_data_len = len;
394 }
395 
396 static void
pdu_send_proxy(struct pdu * pdu)397 pdu_send_proxy(struct pdu *pdu)
398 {
399 
400 	assert(proxy_mode);
401 
402 	pdu_set_data_segment_length(pdu, pdu->pdu_data_len);
403 	kernel_send(pdu);
404 }
405 
406 #endif /* ICL_KERNEL_PROXY */
407 
408 static void
pdu_fail(const struct connection * conn __unused,const char * reason __unused)409 pdu_fail(const struct connection *conn __unused, const char *reason __unused)
410 {
411 }
412 
iscsi_connection(struct portal * portal,freebsd::fd_up fd,const char * host,const struct sockaddr * client_sa)413 iscsi_connection::iscsi_connection(struct portal *portal, freebsd::fd_up fd,
414     const char *host, const struct sockaddr *client_sa) :
415 	conn_portal(portal), conn_fd(std::move(fd)), conn_initiator_addr(host),
416 	conn_initiator_sa(client_sa)
417 {
418 	connection_init(&conn, &conn_ops, proxy_mode);
419 	conn.conn_socket = conn_fd;
420 }
421 
~iscsi_connection()422 iscsi_connection::~iscsi_connection()
423 {
424 	chap_delete(conn_chap);
425 }
426 
427 void
kernel_handoff()428 iscsi_connection::kernel_handoff()
429 {
430 	struct portal_group *pg = conn_portal->portal_group();
431 	struct ctl_iscsi req;
432 
433 	bzero(&req, sizeof(req));
434 
435 	req.type = CTL_ISCSI_HANDOFF;
436 	strlcpy(req.data.handoff.initiator_name, conn_initiator_name.c_str(),
437 	    sizeof(req.data.handoff.initiator_name));
438 	strlcpy(req.data.handoff.initiator_addr, conn_initiator_addr.c_str(),
439 	    sizeof(req.data.handoff.initiator_addr));
440 	if (!conn_initiator_alias.empty()) {
441 		strlcpy(req.data.handoff.initiator_alias,
442 		    conn_initiator_alias.c_str(),
443 		    sizeof(req.data.handoff.initiator_alias));
444 	}
445 	memcpy(req.data.handoff.initiator_isid, conn_initiator_isid,
446 	    sizeof(req.data.handoff.initiator_isid));
447 	strlcpy(req.data.handoff.target_name, conn_target->name(),
448 	    sizeof(req.data.handoff.target_name));
449 	strlcpy(req.data.handoff.offload, pg->offload(),
450 	    sizeof(req.data.handoff.offload));
451 #ifdef ICL_KERNEL_PROXY
452 	if (proxy_mode)
453 		req.data.handoff.connection_id = conn.conn_socket;
454 	else
455 		req.data.handoff.socket = conn.conn_socket;
456 #else
457 	req.data.handoff.socket = conn.conn_socket;
458 #endif
459 	req.data.handoff.portal_group_tag = pg->tag();
460 	if (conn.conn_header_digest == CONN_DIGEST_CRC32C)
461 		req.data.handoff.header_digest = CTL_ISCSI_DIGEST_CRC32C;
462 	if (conn.conn_data_digest == CONN_DIGEST_CRC32C)
463 		req.data.handoff.data_digest = CTL_ISCSI_DIGEST_CRC32C;
464 	req.data.handoff.cmdsn = conn.conn_cmdsn;
465 	req.data.handoff.statsn = conn.conn_statsn;
466 	req.data.handoff.max_recv_data_segment_length =
467 	    conn.conn_max_recv_data_segment_length;
468 	req.data.handoff.max_send_data_segment_length =
469 	    conn.conn_max_send_data_segment_length;
470 	req.data.handoff.max_burst_length = conn.conn_max_burst_length;
471 	req.data.handoff.first_burst_length = conn.conn_first_burst_length;
472 	req.data.handoff.immediate_data = conn.conn_immediate_data;
473 
474 	if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) {
475 		log_err(1, "error issuing CTL_ISCSI ioctl; "
476 		    "dropping connection");
477 	}
478 
479 	if (req.status != CTL_ISCSI_OK) {
480 		log_errx(1, "error returned from CTL iSCSI handoff request: "
481 		    "%s; dropping connection", req.error_str);
482 	}
483 }
484 
485 void
handle()486 iscsi_connection::handle()
487 {
488 	login();
489 	if (conn_session_type == CONN_SESSION_TYPE_NORMAL) {
490 		kernel_handoff();
491 		log_debugx("connection handed off to the kernel");
492 	} else {
493 		assert(conn_session_type == CONN_SESSION_TYPE_DISCOVERY);
494 		discovery();
495 	}
496 }
497 
498 void
handle_connection(freebsd::fd_up fd,const char * host,const struct sockaddr * client_sa)499 iscsi_portal::handle_connection(freebsd::fd_up fd, const char *host,
500     const struct sockaddr *client_sa)
501 {
502 	struct conf *conf = portal_group()->conf();
503 
504 	iscsi_connection conn(this, std::move(fd), host, client_sa);
505 	start_timer(conf->timeout(), true);
506 	kernel_capsicate();
507 	conn.handle();
508 }
509