Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/usr.sbin/ctld/nvmf_discovery.cc
104905 views
1
/*-
2
* SPDX-License-Identifier: BSD-2-Clause
3
*
4
* Copyright (c) 2023-2025 Chelsio Communications, Inc.
5
* Written by: John Baldwin <[email protected]>
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
520