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
datadiscovery_log24 const char *data() const { return buf.data(); }
lengthdiscovery_log25 size_t length() const { return buf.size(); }
26
27 void append(const struct nvme_discovery_log_entry *entry);
28
29 private:
headerdiscovery_log30 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
discovery_log(const struct portal_group * pg)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
append(const struct nvme_discovery_log_entry * entry)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
discovery_controller_filtered(const struct portal_group * pg,const struct sockaddr * client_sa,std::string_view hostnqn,const struct port * port)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
portal_uses_wildcard_address(const struct portal * p)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
init_discovery_log_entry(struct nvme_discovery_log_entry * entry,const struct target * target,const struct portal * portal,const char * wildcard_host)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
build_discovery_log_page(const struct portal_group * pg,int fd,const struct sockaddr * client_sa,const struct nvmf_fabric_connect_data & data)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
update_cc(uint32_t new_cc)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
handle_property_get(const struct nvmf_capsule * nc,const struct nvmf_fabric_prop_get_cmd * pget)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
handle_property_set(const struct nvmf_capsule * nc,const struct nvmf_fabric_prop_set_cmd * pset)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
handle_fabrics_command(const struct nvmf_capsule * nc,const struct nvmf_fabric_cmd * fc)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
handle_identify_command(const struct nvmf_capsule * nc,const struct nvme_command * cmd)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
handle_get_log_page_command(const struct nvmf_capsule * nc,const struct nvme_command * cmd)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
handle_admin_commands()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
discovery_controller(freebsd::fd_up fd,struct nvmf_qpair * qp,const struct discovery_log & discovery_log)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
handle_connection(freebsd::fd_up fd,const char * host __unused,const struct sockaddr * client_sa)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