1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2023-2025 Chelsio Communications, Inc. 5 * Written by: John Baldwin <jhb@FreeBSD.org> 6 */ 7 8 #include <assert.h> 9 #include <errno.h> 10 #include <netdb.h> 11 #include <libiscsiutil.h> 12 #include <libnvmf.h> 13 #include <stdlib.h> 14 #include <string.h> 15 #include <unistd.h> 16 #include <netinet/in.h> 17 18 #include "ctld.hh" 19 #include "nvmf.hh" 20 21 struct discovery_log { 22 discovery_log(const struct portal_group *pg); 23 24 const char *data() const { return buf.data(); } 25 size_t length() const { return buf.size(); } 26 27 void append(const struct nvme_discovery_log_entry *entry); 28 29 private: 30 struct nvme_discovery_log *header() 31 { return reinterpret_cast<struct nvme_discovery_log *>(buf.data()); } 32 33 std::vector<char> buf; 34 }; 35 36 struct discovery_controller { 37 discovery_controller(freebsd::fd_up s, struct nvmf_qpair *qp, 38 const discovery_log &discovery_log); 39 40 void handle_admin_commands(); 41 private: 42 bool update_cc(uint32_t new_cc); 43 void handle_property_get(const struct nvmf_capsule *nc, 44 const struct nvmf_fabric_prop_get_cmd *pget); 45 void handle_property_set(const struct nvmf_capsule *nc, 46 const struct nvmf_fabric_prop_set_cmd *pset); 47 void handle_fabrics_command(const struct nvmf_capsule *nc, 48 const struct nvmf_fabric_cmd *cmd); 49 void handle_identify_command(const struct nvmf_capsule *nc, 50 const struct nvme_command *cmd); 51 void handle_get_log_page_command(const struct nvmf_capsule *nc, 52 const struct nvme_command *cmd); 53 54 struct nvmf_qpair *qp; 55 56 uint64_t cap = 0; 57 uint32_t vs = 0; 58 uint32_t cc = 0; 59 uint32_t csts = 0; 60 61 bool shutdown = false; 62 63 struct nvme_controller_data cdata; 64 65 const struct discovery_log &discovery_log; 66 freebsd::fd_up s; 67 }; 68 69 discovery_log::discovery_log(const struct portal_group *pg) : 70 buf(sizeof(nvme_discovery_log)) 71 { 72 struct nvme_discovery_log *log = header(); 73 74 log->genctr = htole32(pg->conf()->genctr()); 75 log->recfmt = 0; 76 } 77 78 void 79 discovery_log::append(const struct nvme_discovery_log_entry *entry) 80 { 81 const char *cp = reinterpret_cast<const char *>(entry); 82 buf.insert(buf.end(), cp, cp + sizeof(*entry)); 83 84 struct nvme_discovery_log *log = header(); 85 log->numrec = htole32(le32toh(log->numrec) + 1); 86 } 87 88 static bool 89 discovery_controller_filtered(const struct portal_group *pg, 90 const struct sockaddr *client_sa, std::string_view hostnqn, 91 const struct port *port) 92 { 93 const struct target *targ = port->target(); 94 const struct auth_group *ag = port->auth_group(); 95 if (ag == nullptr) 96 ag = targ->auth_group(); 97 98 assert(pg->discovery_filter() != discovery_filter::UNKNOWN); 99 100 if (pg->discovery_filter() >= discovery_filter::PORTAL && 101 !ag->host_permitted(client_sa)) { 102 log_debugx("host address does not match addresses " 103 "allowed for controller \"%s\"; skipping", targ->name()); 104 return true; 105 } 106 107 if (pg->discovery_filter() >= discovery_filter::PORTAL_NAME && 108 !ag->host_permitted(hostnqn) != 0) { 109 log_debugx("HostNQN does not match NQNs " 110 "allowed for controller \"%s\"; skipping", targ->name()); 111 return true; 112 } 113 114 /* XXX: auth not yet implemented for NVMe */ 115 116 return false; 117 } 118 119 static bool 120 portal_uses_wildcard_address(const struct portal *p) 121 { 122 const struct addrinfo *ai = p->ai(); 123 124 switch (ai->ai_family) { 125 case AF_INET: 126 { 127 const struct sockaddr_in *sin; 128 129 sin = (const struct sockaddr_in *)ai->ai_addr; 130 return sin->sin_addr.s_addr == htonl(INADDR_ANY); 131 } 132 case AF_INET6: 133 { 134 const struct sockaddr_in6 *sin6; 135 136 sin6 = (const struct sockaddr_in6 *)ai->ai_addr; 137 return memcmp(&sin6->sin6_addr, &in6addr_any, 138 sizeof(in6addr_any)) == 0; 139 } 140 default: 141 __assert_unreachable(); 142 } 143 } 144 145 static bool 146 init_discovery_log_entry(struct nvme_discovery_log_entry *entry, 147 const struct target *target, const struct portal *portal, 148 const char *wildcard_host) 149 { 150 /* 151 * The TCP port for I/O controllers might not be fixed, so 152 * fetch the sockaddr of the socket to determine which port 153 * the kernel chose. 154 */ 155 struct sockaddr_storage ss; 156 socklen_t len = sizeof(ss); 157 if (getsockname(portal->socket(), (struct sockaddr *)&ss, &len) == -1) { 158 log_warn("Failed getsockname building discovery log entry"); 159 return false; 160 } 161 162 const struct nvmf_association_params *aparams = 163 static_cast<const nvmf_portal *>(portal)->aparams(); 164 165 memset(entry, 0, sizeof(*entry)); 166 entry->trtype = NVMF_TRTYPE_TCP; 167 int error = getnameinfo((struct sockaddr *)&ss, len, 168 (char *)entry->traddr, sizeof(entry->traddr), 169 (char *)entry->trsvcid, sizeof(entry->trsvcid), 170 NI_NUMERICHOST | NI_NUMERICSERV); 171 if (error != 0) { 172 log_warnx("Failed getnameinfo building discovery log entry: %s", 173 gai_strerror(error)); 174 return false; 175 } 176 177 if (portal_uses_wildcard_address(portal)) 178 strncpy((char *)entry->traddr, wildcard_host, 179 sizeof(entry->traddr)); 180 switch (portal->ai()->ai_family) { 181 case AF_INET: 182 entry->adrfam = NVMF_ADRFAM_IPV4; 183 break; 184 case AF_INET6: 185 entry->adrfam = NVMF_ADRFAM_IPV6; 186 break; 187 default: 188 __assert_unreachable(); 189 } 190 entry->subtype = NVMF_SUBTYPE_NVME; 191 if (!aparams->sq_flow_control) 192 entry->treq |= (1 << 2); 193 entry->portid = htole16(portal->portal_group()->tag()); 194 entry->cntlid = htole16(NVMF_CNTLID_DYNAMIC); 195 entry->aqsz = aparams->max_admin_qsize; 196 strncpy((char *)entry->subnqn, target->name(), sizeof(entry->subnqn)); 197 return true; 198 } 199 200 static discovery_log 201 build_discovery_log_page(const struct portal_group *pg, int fd, 202 const struct sockaddr *client_sa, 203 const struct nvmf_fabric_connect_data &data) 204 { 205 discovery_log discovery_log(pg); 206 207 struct sockaddr_storage ss; 208 socklen_t len = sizeof(ss); 209 if (getsockname(fd, (struct sockaddr *)&ss, &len) == -1) { 210 log_warn("build_discovery_log_page: getsockname"); 211 return discovery_log; 212 } 213 214 char wildcard_host[NI_MAXHOST]; 215 int error = getnameinfo((struct sockaddr *)&ss, len, wildcard_host, 216 sizeof(wildcard_host), NULL, 0, NI_NUMERICHOST); 217 if (error != 0) { 218 log_warnx("build_discovery_log_page: getnameinfo: %s", 219 gai_strerror(error)); 220 return discovery_log; 221 } 222 223 const char *nqn = (const char *)data.hostnqn; 224 std::string hostnqn(nqn, strnlen(nqn, sizeof(data.hostnqn))); 225 for (const auto &kv : pg->ports()) { 226 const struct port *port = kv.second; 227 if (discovery_controller_filtered(pg, client_sa, hostnqn, port)) 228 continue; 229 230 for (const portal_up &portal : pg->portals()) { 231 if (portal->protocol() != portal_protocol::NVME_TCP) 232 continue; 233 234 if (portal_uses_wildcard_address(portal.get()) && 235 portal->ai()->ai_family != client_sa->sa_family) 236 continue; 237 238 struct nvme_discovery_log_entry entry; 239 if (init_discovery_log_entry(&entry, port->target(), 240 portal.get(), wildcard_host)) 241 discovery_log.append(&entry); 242 } 243 } 244 245 return discovery_log; 246 } 247 248 bool 249 discovery_controller::update_cc(uint32_t new_cc) 250 { 251 uint32_t changes; 252 253 if (shutdown) 254 return false; 255 if (!nvmf_validate_cc(qp, cap, cc, new_cc)) 256 return false; 257 258 changes = cc ^ new_cc; 259 cc = new_cc; 260 261 /* Handle shutdown requests. */ 262 if (NVMEV(NVME_CC_REG_SHN, changes) != 0 && 263 NVMEV(NVME_CC_REG_SHN, new_cc) != 0) { 264 csts &= ~NVMEM(NVME_CSTS_REG_SHST); 265 csts |= NVMEF(NVME_CSTS_REG_SHST, NVME_SHST_COMPLETE); 266 shutdown = true; 267 } 268 269 if (NVMEV(NVME_CC_REG_EN, changes) != 0) { 270 if (NVMEV(NVME_CC_REG_EN, new_cc) == 0) { 271 /* Controller reset. */ 272 csts = 0; 273 shutdown = true; 274 } else 275 csts |= NVMEF(NVME_CSTS_REG_RDY, 1); 276 } 277 return true; 278 } 279 280 void 281 discovery_controller::handle_property_get(const struct nvmf_capsule *nc, 282 const struct nvmf_fabric_prop_get_cmd *pget) 283 { 284 struct nvmf_fabric_prop_get_rsp rsp; 285 286 nvmf_init_cqe(&rsp, nc, 0); 287 288 switch (le32toh(pget->ofst)) { 289 case NVMF_PROP_CAP: 290 if (pget->attrib.size != NVMF_PROP_SIZE_8) 291 goto error; 292 rsp.value.u64 = htole64(cap); 293 break; 294 case NVMF_PROP_VS: 295 if (pget->attrib.size != NVMF_PROP_SIZE_4) 296 goto error; 297 rsp.value.u32.low = htole32(vs); 298 break; 299 case NVMF_PROP_CC: 300 if (pget->attrib.size != NVMF_PROP_SIZE_4) 301 goto error; 302 rsp.value.u32.low = htole32(cc); 303 break; 304 case NVMF_PROP_CSTS: 305 if (pget->attrib.size != NVMF_PROP_SIZE_4) 306 goto error; 307 rsp.value.u32.low = htole32(csts); 308 break; 309 default: 310 goto error; 311 } 312 313 nvmf_send_response(nc, &rsp); 314 return; 315 error: 316 nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD); 317 } 318 319 void 320 discovery_controller::handle_property_set(const struct nvmf_capsule *nc, 321 const struct nvmf_fabric_prop_set_cmd *pset) 322 { 323 switch (le32toh(pset->ofst)) { 324 case NVMF_PROP_CC: 325 if (pset->attrib.size != NVMF_PROP_SIZE_4) 326 goto error; 327 if (!update_cc(le32toh(pset->value.u32.low))) 328 goto error; 329 break; 330 default: 331 goto error; 332 } 333 334 nvmf_send_success(nc); 335 return; 336 error: 337 nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD); 338 } 339 340 void 341 discovery_controller::handle_fabrics_command(const struct nvmf_capsule *nc, 342 const struct nvmf_fabric_cmd *fc) 343 { 344 switch (fc->fctype) { 345 case NVMF_FABRIC_COMMAND_PROPERTY_GET: 346 handle_property_get(nc, 347 (const struct nvmf_fabric_prop_get_cmd *)fc); 348 break; 349 case NVMF_FABRIC_COMMAND_PROPERTY_SET: 350 handle_property_set(nc, 351 (const struct nvmf_fabric_prop_set_cmd *)fc); 352 break; 353 case NVMF_FABRIC_COMMAND_CONNECT: 354 log_warnx("CONNECT command on connected queue"); 355 nvmf_send_generic_error(nc, NVME_SC_COMMAND_SEQUENCE_ERROR); 356 break; 357 case NVMF_FABRIC_COMMAND_DISCONNECT: 358 log_warnx("DISCONNECT command on admin queue"); 359 nvmf_send_error(nc, NVME_SCT_COMMAND_SPECIFIC, 360 NVMF_FABRIC_SC_INVALID_QUEUE_TYPE); 361 break; 362 default: 363 log_warnx("Unsupported fabrics command %#x", fc->fctype); 364 nvmf_send_generic_error(nc, NVME_SC_INVALID_OPCODE); 365 break; 366 } 367 } 368 369 void 370 discovery_controller::handle_identify_command(const struct nvmf_capsule *nc, 371 const struct nvme_command *cmd) 372 { 373 uint8_t cns; 374 375 cns = le32toh(cmd->cdw10) & 0xFF; 376 switch (cns) { 377 case 1: 378 break; 379 default: 380 log_warnx("Unsupported CNS %#x for IDENTIFY", cns); 381 goto error; 382 } 383 384 nvmf_send_controller_data(nc, &cdata, sizeof(cdata)); 385 return; 386 error: 387 nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD); 388 } 389 390 void 391 discovery_controller::handle_get_log_page_command(const struct nvmf_capsule *nc, 392 const struct nvme_command *cmd) 393 { 394 uint64_t offset; 395 uint32_t length; 396 397 switch (nvmf_get_log_page_id(cmd)) { 398 case NVME_LOG_DISCOVERY: 399 break; 400 default: 401 log_warnx("Unsupported log page %u for discovery controller", 402 nvmf_get_log_page_id(cmd)); 403 goto error; 404 } 405 406 offset = nvmf_get_log_page_offset(cmd); 407 if (offset >= discovery_log.length()) 408 goto error; 409 410 length = nvmf_get_log_page_length(cmd); 411 if (length > discovery_log.length() - offset) 412 length = discovery_log.length() - offset; 413 414 nvmf_send_controller_data(nc, discovery_log.data() + offset, length); 415 return; 416 error: 417 nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD); 418 } 419 420 void 421 discovery_controller::handle_admin_commands() 422 { 423 for (;;) { 424 struct nvmf_capsule *nc; 425 int error = nvmf_controller_receive_capsule(qp, &nc); 426 if (error != 0) { 427 if (error != ECONNRESET) 428 log_warnc(error, 429 "Failed to read command capsule"); 430 break; 431 } 432 nvmf_capsule_up nc_guard(nc); 433 434 const struct nvme_command *cmd = 435 (const struct nvme_command *)nvmf_capsule_sqe(nc); 436 437 /* 438 * Only permit Fabrics commands while a controller is 439 * disabled. 440 */ 441 if (NVMEV(NVME_CC_REG_EN, cc) == 0 && 442 cmd->opc != NVME_OPC_FABRICS_COMMANDS) { 443 log_warnx("Unsupported admin opcode %#x while disabled\n", 444 cmd->opc); 445 nvmf_send_generic_error(nc, 446 NVME_SC_COMMAND_SEQUENCE_ERROR); 447 continue; 448 } 449 450 switch (cmd->opc) { 451 case NVME_OPC_FABRICS_COMMANDS: 452 handle_fabrics_command(nc, 453 (const struct nvmf_fabric_cmd *)cmd); 454 break; 455 case NVME_OPC_IDENTIFY: 456 handle_identify_command(nc, cmd); 457 break; 458 case NVME_OPC_GET_LOG_PAGE: 459 handle_get_log_page_command(nc, cmd); 460 break; 461 default: 462 log_warnx("Unsupported admin opcode %#x", cmd->opc); 463 nvmf_send_generic_error(nc, NVME_SC_INVALID_OPCODE); 464 break; 465 } 466 } 467 } 468 469 discovery_controller::discovery_controller(freebsd::fd_up fd, 470 struct nvmf_qpair *qp, const struct discovery_log &discovery_log) : 471 qp(qp), discovery_log(discovery_log), s(std::move(fd)) 472 { 473 nvmf_init_discovery_controller_data(qp, &cdata); 474 cap = nvmf_controller_cap(qp); 475 vs = cdata.ver; 476 } 477 478 void 479 nvmf_discovery_portal::handle_connection(freebsd::fd_up fd, 480 const char *host __unused, const struct sockaddr *client_sa) 481 { 482 struct nvmf_qpair_params qparams; 483 memset(&qparams, 0, sizeof(qparams)); 484 qparams.tcp.fd = fd; 485 486 struct nvmf_capsule *nc = NULL; 487 struct nvmf_fabric_connect_data data; 488 nvmf_qpair_up qp(nvmf_accept(association(), &qparams, &nc, &data)); 489 if (!qp) { 490 log_warnx("Failed to create NVMe discovery qpair: %s", 491 nvmf_association_error(association())); 492 return; 493 } 494 nvmf_capsule_up nc_guard(nc); 495 496 if (strncmp((char *)data.subnqn, NVMF_DISCOVERY_NQN, 497 sizeof(data.subnqn)) != 0) { 498 log_warnx("Discovery NVMe qpair with invalid SubNQN: %.*s", 499 (int)sizeof(data.subnqn), data.subnqn); 500 nvmf_connect_invalid_parameters(nc, true, 501 offsetof(struct nvmf_fabric_connect_data, subnqn)); 502 return; 503 } 504 505 /* Just use a controller ID of 1 for all discovery controllers. */ 506 int error = nvmf_finish_accept(nc, 1); 507 if (error != 0) { 508 log_warnc(error, "Failed to send NVMe CONNECT reponse"); 509 return; 510 } 511 nc_guard.reset(); 512 513 discovery_log discovery_log = build_discovery_log_page(portal_group(), 514 fd, client_sa, data); 515 516 discovery_controller controller(std::move(fd), qp.get(), discovery_log); 517 controller.handle_admin_commands(); 518 } 519