1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2023-2024 Chelsio Communications, Inc.
5 * Written by: John Baldwin <jhb@FreeBSD.org>
6 */
7
8 #include <sys/socket.h>
9 #include <netinet/in.h>
10 #include <arpa/inet.h>
11 #include <err.h>
12 #include <libnvmf.h>
13 #include <netdb.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <sysexits.h>
18 #include <unistd.h>
19
20 #include "fabrics.h"
21
22 /*
23 * Subroutines shared by several Fabrics commands.
24 */
25 static char nqn[NVMF_NQN_MAX_LEN];
26 static uint8_t hostid[16];
27 static bool hostid_initted = false;
28
29 static bool
init_hostid(void)30 init_hostid(void)
31 {
32 int error;
33
34 if (hostid_initted)
35 return (true);
36
37 error = nvmf_hostid_from_hostuuid(hostid);
38 if (error != 0) {
39 warnc(error, "Failed to generate hostid");
40 return (false);
41 }
42 error = nvmf_nqn_from_hostuuid(nqn);
43 if (error != 0) {
44 warnc(error, "Failed to generate host NQN");
45 return (false);
46 }
47
48 hostid_initted = true;
49 return (true);
50 }
51
52 const char *
nvmf_default_hostnqn(void)53 nvmf_default_hostnqn(void)
54 {
55 if (!init_hostid())
56 exit(EX_IOERR);
57 return (nqn);
58 }
59
60 void
nvmf_parse_address(const char * in_address,const char ** address,const char ** port,char ** tofree)61 nvmf_parse_address(const char *in_address, const char **address,
62 const char **port, char **tofree)
63 {
64 char *cp;
65
66 /*
67 * Accepts the following address formats:
68 *
69 * [IPv6 address]:port
70 * IPv4 address:port
71 * hostname:port
72 * [IPv6 address]
73 * IPv6 address
74 * IPv4 address
75 * hostname
76 */
77 if (in_address[0] == '[') {
78 /* IPv6 address in square brackets. */
79 cp = strchr(in_address + 1, ']');
80 if (cp == NULL || cp == in_address + 1)
81 errx(EX_USAGE, "Invalid address %s", in_address);
82 *tofree = strndup(in_address + 1, cp - (in_address + 1));
83 *address = *tofree;
84
85 /* Skip over ']' */
86 cp++;
87 switch (*cp) {
88 case '\0':
89 *port = NULL;
90 return;
91 case ':':
92 if (cp[1] != '\0') {
93 *port = cp + 1;
94 return;
95 }
96 /* FALLTHROUGH */
97 default:
98 errx(EX_USAGE, "Invalid address %s", in_address);
99 }
100 }
101
102 /* Look for the first colon. */
103 cp = strchr(in_address, ':');
104 if (cp == NULL) {
105 *address = in_address;
106 *port = NULL;
107 *tofree = NULL;
108 return;
109 }
110
111 /* If there is another colon, assume this is an IPv6 address. */
112 if (strchr(cp + 1, ':') != NULL) {
113 *address = in_address;
114 *port = NULL;
115 *tofree = NULL;
116 return;
117 }
118
119 /* Both strings on either side of the colon must be non-empty. */
120 if (cp == in_address || cp[1] == '\0')
121 errx(EX_USAGE, "Invalid address %s", in_address);
122
123 *tofree = strndup(in_address, cp - in_address);
124 *address = *tofree;
125
126 /* Skip over ':' */
127 *port = cp + 1;
128 }
129
130 uint16_t
nvmf_parse_cntlid(const char * cntlid)131 nvmf_parse_cntlid(const char *cntlid)
132 {
133 u_long value;
134
135 if (strcasecmp(cntlid, "dynamic") == 0)
136 return (NVMF_CNTLID_DYNAMIC);
137 else if (strcasecmp(cntlid, "static") == 0)
138 return (NVMF_CNTLID_STATIC_ANY);
139 else {
140 value = strtoul(cntlid, NULL, 0);
141
142 if (value > NVMF_CNTLID_STATIC_MAX)
143 errx(EX_USAGE, "Invalid controller ID");
144
145 return (value);
146 }
147 }
148
149 static bool
tcp_qpair_params(struct nvmf_qpair_params * params,int adrfam,const char * address,const char * port,struct addrinfo ** aip,struct addrinfo ** listp)150 tcp_qpair_params(struct nvmf_qpair_params *params, int adrfam,
151 const char *address, const char *port, struct addrinfo **aip,
152 struct addrinfo **listp)
153 {
154 struct addrinfo hints, *ai, *list;
155 int error, s;
156
157 memset(&hints, 0, sizeof(hints));
158 hints.ai_family = adrfam;
159 hints.ai_protocol = IPPROTO_TCP;
160 error = getaddrinfo(address, port, &hints, &list);
161 if (error != 0) {
162 warnx("%s", gai_strerror(error));
163 return (false);
164 }
165
166 for (ai = list; ai != NULL; ai = ai->ai_next) {
167 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
168 if (s == -1)
169 continue;
170
171 if (connect(s, ai->ai_addr, ai->ai_addrlen) != 0) {
172 close(s);
173 continue;
174 }
175
176 params->tcp.fd = s;
177 if (listp != NULL) {
178 *aip = ai;
179 *listp = list;
180 } else
181 freeaddrinfo(list);
182 return (true);
183 }
184 warn("Failed to connect to controller at %s:%s", address, port);
185 freeaddrinfo(list);
186 return (false);
187 }
188
189 static bool
tcp_qpair_params_ai(struct nvmf_qpair_params * params,struct addrinfo * ai)190 tcp_qpair_params_ai(struct nvmf_qpair_params *params, struct addrinfo *ai)
191 {
192 int s;
193
194 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
195 if (s == -1)
196 return (false);
197
198 if (connect(s, ai->ai_addr, ai->ai_addrlen) != 0) {
199 close(s);
200 return (false);
201 }
202
203 params->tcp.fd = s;
204 return (true);
205 }
206
207 static void
tcp_discovery_association_params(struct nvmf_association_params * params)208 tcp_discovery_association_params(struct nvmf_association_params *params)
209 {
210 params->tcp.pda = 0;
211 params->tcp.header_digests = false;
212 params->tcp.data_digests = false;
213 params->tcp.maxr2t = 1;
214 }
215
216 struct nvmf_qpair *
connect_discovery_adminq(enum nvmf_trtype trtype,const char * address,const char * port,const char * hostnqn)217 connect_discovery_adminq(enum nvmf_trtype trtype, const char *address,
218 const char *port, const char *hostnqn)
219 {
220 struct nvmf_association_params aparams;
221 struct nvmf_qpair_params qparams;
222 struct nvmf_association *na;
223 struct nvmf_qpair *qp;
224 uint64_t cap, cc, csts;
225 int error, timo;
226
227 memset(&aparams, 0, sizeof(aparams));
228 aparams.sq_flow_control = false;
229 switch (trtype) {
230 case NVMF_TRTYPE_TCP:
231 /* 7.4.9.3 Default port for discovery */
232 if (port == NULL)
233 port = "8009";
234 tcp_discovery_association_params(&aparams);
235 break;
236 default:
237 errx(EX_UNAVAILABLE, "Unsupported transport %s",
238 nvmf_transport_type(trtype));
239 }
240
241 if (!init_hostid())
242 exit(EX_IOERR);
243 if (hostnqn != NULL) {
244 if (!nvmf_nqn_valid(hostnqn))
245 errx(EX_USAGE, "Invalid HostNQN %s", hostnqn);
246 } else
247 hostnqn = nqn;
248
249 na = nvmf_allocate_association(trtype, false, &aparams);
250 if (na == NULL)
251 err(EX_IOERR, "Failed to create discovery association");
252 memset(&qparams, 0, sizeof(qparams));
253 qparams.admin = true;
254 if (!tcp_qpair_params(&qparams, AF_UNSPEC, address, port, NULL, NULL))
255 exit(EX_NOHOST);
256 qp = nvmf_connect(na, &qparams, 0, NVME_MIN_ADMIN_ENTRIES, hostid,
257 NVMF_CNTLID_DYNAMIC, NVMF_DISCOVERY_NQN, hostnqn, 0);
258 if (qp == NULL)
259 errx(EX_IOERR, "Failed to connect to discovery controller: %s",
260 nvmf_association_error(na));
261 nvmf_free_association(na);
262
263 /* Fetch Controller Capabilities Property */
264 error = nvmf_read_property(qp, NVMF_PROP_CAP, 8, &cap);
265 if (error != 0)
266 errc(EX_IOERR, error, "Failed to fetch CAP");
267
268 /* Set Controller Configuration Property (CC.EN=1) */
269 error = nvmf_read_property(qp, NVMF_PROP_CC, 4, &cc);
270 if (error != 0)
271 errc(EX_IOERR, error, "Failed to fetch CC");
272
273 /* Clear known fields preserving any reserved fields. */
274 cc &= ~(NVMEM(NVME_CC_REG_SHN) | NVMEM(NVME_CC_REG_AMS) |
275 NVMEM(NVME_CC_REG_MPS) | NVMEM(NVME_CC_REG_CSS));
276
277 /* Leave AMS, MPS, and CSS as 0. */
278
279 cc |= NVMEF(NVME_CC_REG_EN, 1);
280
281 error = nvmf_write_property(qp, NVMF_PROP_CC, 4, cc);
282 if (error != 0)
283 errc(EX_IOERR, error, "Failed to set CC");
284
285 /* Wait for CSTS.RDY in Controller Status */
286 timo = NVME_CAP_LO_TO(cap);
287 for (;;) {
288 error = nvmf_read_property(qp, NVMF_PROP_CSTS, 4, &csts);
289 if (error != 0)
290 errc(EX_IOERR, error, "Failed to fetch CSTS");
291
292 if (NVMEV(NVME_CSTS_REG_RDY, csts) != 0)
293 break;
294
295 if (timo == 0)
296 errx(EX_IOERR, "Controller failed to become ready");
297 timo--;
298 usleep(500 * 1000);
299 }
300
301 return (qp);
302 }
303
304 /*
305 * XXX: Should this accept the admin queue size as a parameter rather
306 * than always using NVMF_MIN_ADMIN_MAX_SQ_SIZE?
307 */
308 static int
connect_nvm_adminq(struct nvmf_association * na,const struct nvmf_qpair_params * params,struct nvmf_qpair ** qpp,uint16_t cntlid,const char * subnqn,const char * hostnqn,uint32_t kato,uint16_t * mqes)309 connect_nvm_adminq(struct nvmf_association *na,
310 const struct nvmf_qpair_params *params, struct nvmf_qpair **qpp,
311 uint16_t cntlid, const char *subnqn, const char *hostnqn, uint32_t kato,
312 uint16_t *mqes)
313 {
314 struct nvmf_qpair *qp;
315 uint64_t cap, cc, csts;
316 u_int mps, mpsmin, mpsmax;
317 int error, timo;
318
319 qp = nvmf_connect(na, params, 0, NVMF_MIN_ADMIN_MAX_SQ_SIZE, hostid,
320 cntlid, subnqn, hostnqn, kato);
321 if (qp == NULL) {
322 warnx("Failed to connect to NVM controller %s: %s", subnqn,
323 nvmf_association_error(na));
324 return (EX_IOERR);
325 }
326
327 /* Fetch Controller Capabilities Property */
328 error = nvmf_read_property(qp, NVMF_PROP_CAP, 8, &cap);
329 if (error != 0) {
330 warnc(error, "Failed to fetch CAP");
331 nvmf_free_qpair(qp);
332 return (EX_IOERR);
333 }
334
335 /* Require the NVM command set. */
336 if (NVME_CAP_HI_CSS_NVM(cap >> 32) == 0) {
337 warnx("Controller %s does not support the NVM command set",
338 subnqn);
339 nvmf_free_qpair(qp);
340 return (EX_UNAVAILABLE);
341 }
342
343 *mqes = NVME_CAP_LO_MQES(cap);
344
345 /* Prefer native host page size if it fits. */
346 mpsmin = NVMEV(NVME_CAP_HI_REG_MPSMIN, cap >> 32);
347 mpsmax = NVMEV(NVME_CAP_HI_REG_MPSMAX, cap >> 32);
348 mps = ffs(getpagesize()) - 1;
349 if (mps < mpsmin + NVME_MPS_SHIFT)
350 mps = mpsmin;
351 else if (mps > mpsmax + NVME_MPS_SHIFT)
352 mps = mpsmax;
353 else
354 mps -= NVME_MPS_SHIFT;
355
356 /* Configure controller. */
357 error = nvmf_read_property(qp, NVMF_PROP_CC, 4, &cc);
358 if (error != 0) {
359 warnc(error, "Failed to fetch CC");
360 nvmf_free_qpair(qp);
361 return (EX_IOERR);
362 }
363
364 /* Clear known fields preserving any reserved fields. */
365 cc &= ~(NVMEM(NVME_CC_REG_IOCQES) | NVMEM(NVME_CC_REG_IOSQES) |
366 NVMEM(NVME_CC_REG_SHN) | NVMEM(NVME_CC_REG_AMS) |
367 NVMEM(NVME_CC_REG_MPS) | NVMEM(NVME_CC_REG_CSS));
368
369 cc |= NVMEF(NVME_CC_REG_IOCQES, 4); /* CQE entry size == 16 */
370 cc |= NVMEF(NVME_CC_REG_IOSQES, 6); /* SEQ entry size == 64 */
371 cc |= NVMEF(NVME_CC_REG_AMS, 0); /* AMS 0 (Round-robin) */
372 cc |= NVMEF(NVME_CC_REG_MPS, mps);
373 cc |= NVMEF(NVME_CC_REG_CSS, 0); /* NVM command set */
374 cc |= NVMEF(NVME_CC_REG_EN, 1); /* EN = 1 */
375
376 error = nvmf_write_property(qp, NVMF_PROP_CC, 4, cc);
377 if (error != 0) {
378 warnc(error, "Failed to set CC");
379 nvmf_free_qpair(qp);
380 return (EX_IOERR);
381 }
382
383 /* Wait for CSTS.RDY in Controller Status */
384 timo = NVME_CAP_LO_TO(cap);
385 for (;;) {
386 error = nvmf_read_property(qp, NVMF_PROP_CSTS, 4, &csts);
387 if (error != 0) {
388 warnc(error, "Failed to fetch CSTS");
389 nvmf_free_qpair(qp);
390 return (EX_IOERR);
391 }
392
393 if (NVMEV(NVME_CSTS_REG_RDY, csts) != 0)
394 break;
395
396 if (timo == 0) {
397 warnx("Controller failed to become ready");
398 nvmf_free_qpair(qp);
399 return (EX_IOERR);
400 }
401 timo--;
402 usleep(500 * 1000);
403 }
404
405 *qpp = qp;
406 return (0);
407 }
408
409 static void
shutdown_controller(struct nvmf_qpair * qp)410 shutdown_controller(struct nvmf_qpair *qp)
411 {
412 uint64_t cc;
413 int error;
414
415 error = nvmf_read_property(qp, NVMF_PROP_CC, 4, &cc);
416 if (error != 0) {
417 warnc(error, "Failed to fetch CC");
418 goto out;
419 }
420
421 cc |= NVMEF(NVME_CC_REG_SHN, NVME_SHN_NORMAL);
422
423 error = nvmf_write_property(qp, NVMF_PROP_CC, 4, cc);
424 if (error != 0) {
425 warnc(error, "Failed to set CC to trigger shutdown");
426 goto out;
427 }
428
429 out:
430 nvmf_free_qpair(qp);
431 }
432
433 /* Returns a value from <sysexits.h> */
434 int
connect_nvm_queues(const struct nvmf_association_params * aparams,enum nvmf_trtype trtype,int adrfam,const char * address,const char * port,uint16_t cntlid,const char * subnqn,const char * hostnqn,uint32_t kato,struct nvmf_qpair ** admin,struct nvmf_qpair ** io,u_int num_io_queues,u_int queue_size,struct nvme_controller_data * cdata)435 connect_nvm_queues(const struct nvmf_association_params *aparams,
436 enum nvmf_trtype trtype, int adrfam, const char *address,
437 const char *port, uint16_t cntlid, const char *subnqn, const char *hostnqn,
438 uint32_t kato, struct nvmf_qpair **admin, struct nvmf_qpair **io,
439 u_int num_io_queues, u_int queue_size, struct nvme_controller_data *cdata)
440 {
441 struct nvmf_qpair_params qparams;
442 struct nvmf_association *na;
443 struct addrinfo *ai, *list;
444 u_int queues;
445 int error;
446 uint16_t mqes;
447
448 switch (trtype) {
449 case NVMF_TRTYPE_TCP:
450 break;
451 default:
452 warnx("Unsupported transport %s", nvmf_transport_type(trtype));
453 return (EX_UNAVAILABLE);
454 }
455
456 if (!init_hostid())
457 return (EX_IOERR);
458 if (hostnqn == NULL || !nvmf_nqn_valid(hostnqn)) {
459 warnx("Invalid HostNQN %s", hostnqn);
460 return (EX_USAGE);
461 }
462
463 /* Association. */
464 na = nvmf_allocate_association(trtype, false, aparams);
465 if (na == NULL) {
466 warn("Failed to create association for %s", subnqn);
467 return (EX_IOERR);
468 }
469
470 /* Admin queue. */
471 memset(&qparams, 0, sizeof(qparams));
472 qparams.admin = true;
473 if (!tcp_qpair_params(&qparams, adrfam, address, port, &ai, &list)) {
474 nvmf_free_association(na);
475 return (EX_NOHOST);
476 }
477 error = connect_nvm_adminq(na, &qparams, admin, cntlid, subnqn, hostnqn,
478 kato, &mqes);
479 if (error != 0) {
480 nvmf_free_association(na);
481 freeaddrinfo(list);
482 return (error);
483 }
484
485 /* Validate I/O queue size. */
486 memset(io, 0, sizeof(*io) * num_io_queues);
487 if (queue_size == 0)
488 queue_size = (u_int)mqes + 1;
489 else if (queue_size > (u_int)mqes + 1) {
490 warnx("I/O queue size exceeds controller maximum (%u)",
491 mqes + 1);
492 error = EX_USAGE;
493 goto out;
494 }
495
496 /* Fetch controller data. */
497 error = nvmf_host_identify_controller(*admin, cdata);
498 if (error != 0) {
499 warnc(error, "Failed to fetch controller data for %s", subnqn);
500 error = EX_IOERR;
501 goto out;
502 }
503
504 nvmf_update_assocation(na, cdata);
505
506 error = nvmf_host_request_queues(*admin, num_io_queues, &queues);
507 if (error != 0) {
508 warnc(error, "Failed to request I/O queues");
509 error = EX_IOERR;
510 goto out;
511 }
512 if (queues < num_io_queues) {
513 warnx("Controller enabled fewer I/O queues (%u) than requested (%u)",
514 queues, num_io_queues);
515 error = EX_PROTOCOL;
516 goto out;
517 }
518
519 /* I/O queues. */
520 for (u_int i = 0; i < num_io_queues; i++) {
521 memset(&qparams, 0, sizeof(qparams));
522 qparams.admin = false;
523 if (!tcp_qpair_params_ai(&qparams, ai)) {
524 warn("Failed to connect to controller at %s:%s",
525 address, port);
526 error = EX_NOHOST;
527 goto out;
528 }
529 io[i] = nvmf_connect(na, &qparams, i + 1, queue_size, hostid,
530 nvmf_cntlid(*admin), subnqn, hostnqn, 0);
531 if (io[i] == NULL) {
532 warnx("Failed to create I/O queue: %s",
533 nvmf_association_error(na));
534 error = EX_IOERR;
535 goto out;
536 }
537 }
538 nvmf_free_association(na);
539 freeaddrinfo(list);
540 return (0);
541
542 out:
543 disconnect_nvm_queues(*admin, io, num_io_queues);
544 nvmf_free_association(na);
545 freeaddrinfo(list);
546 return (error);
547 }
548
549 void
disconnect_nvm_queues(struct nvmf_qpair * admin,struct nvmf_qpair ** io,u_int num_io_queues)550 disconnect_nvm_queues(struct nvmf_qpair *admin, struct nvmf_qpair **io,
551 u_int num_io_queues)
552 {
553 for (u_int i = 0; i < num_io_queues; i++) {
554 if (io[i] == NULL)
555 break;
556 nvmf_free_qpair(io[i]);
557 }
558 shutdown_controller(admin);
559 }
560