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 { 59 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 { 69 iscsi_portal_group(struct conf *conf, std::string_view name) : 70 portal_group(conf, name) {} 71 72 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 { 90 iscsi_port(struct target *target, struct portal_group *pg, 91 auth_group_sp ag) : 92 portal_group_port(target, pg, ag) {} 93 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 { 106 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 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 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 154 iscsi_portal_group::allocate_tag() 155 { 156 set_tag(++last_portal_group_tag); 157 } 158 159 bool 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 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 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 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 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 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 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 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 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 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 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 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 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 * 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 * 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 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 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 409 pdu_fail(const struct connection *conn __unused, const char *reason __unused) 410 { 411 } 412 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 422 iscsi_connection::~iscsi_connection() 423 { 424 chap_delete(conn_chap); 425 } 426 427 void 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 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 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