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 <err.h>
10 #include <libnvmf.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <sysexits.h>
14 #include <unistd.h>
15
16 #include "comnd.h"
17 #include "fabrics.h"
18
19 /*
20 * Settings that are currently hardcoded but could be exposed to the
21 * user via additional command line options:
22 *
23 * - ADMIN queue entries
24 * - MaxR2T
25 */
26
27 static struct options {
28 const char *transport;
29 const char *address;
30 const char *cntlid;
31 const char *subnqn;
32 const char *hostnqn;
33 uint32_t kato;
34 uint16_t num_io_queues;
35 uint16_t queue_size;
36 bool data_digests;
37 bool flow_control;
38 bool header_digests;
39 } opt = {
40 .transport = "tcp",
41 .address = NULL,
42 .cntlid = "dynamic",
43 .subnqn = NULL,
44 .hostnqn = NULL,
45 .kato = NVMF_KATO_DEFAULT / 1000,
46 .num_io_queues = 1,
47 .queue_size = 0,
48 .data_digests = false,
49 .flow_control = false,
50 .header_digests = false,
51 };
52
53 static void
tcp_association_params(struct nvmf_association_params * params)54 tcp_association_params(struct nvmf_association_params *params)
55 {
56 params->tcp.pda = 0;
57 params->tcp.header_digests = opt.header_digests;
58 params->tcp.data_digests = opt.data_digests;
59 /* XXX */
60 params->tcp.maxr2t = 1;
61 }
62
63 static int
connect_nvm_controller(enum nvmf_trtype trtype,int adrfam,const char * address,const char * port,uint16_t cntlid,const char * subnqn,const struct nvme_discovery_log_entry * dle)64 connect_nvm_controller(enum nvmf_trtype trtype, int adrfam, const char *address,
65 const char *port, uint16_t cntlid, const char *subnqn,
66 const struct nvme_discovery_log_entry *dle)
67 {
68 struct nvme_controller_data cdata;
69 struct nvme_discovery_log_entry dle_thunk;
70 struct nvmf_association_params aparams;
71 struct nvmf_qpair *admin, **io;
72 const char *hostnqn;
73 int error;
74
75 memset(&aparams, 0, sizeof(aparams));
76 aparams.sq_flow_control = opt.flow_control;
77 switch (trtype) {
78 case NVMF_TRTYPE_TCP:
79 tcp_association_params(&aparams);
80 break;
81 default:
82 warnx("Unsupported transport %s", nvmf_transport_type(trtype));
83 return (EX_UNAVAILABLE);
84 }
85
86 hostnqn = opt.hostnqn;
87 if (hostnqn == NULL)
88 hostnqn = nvmf_default_hostnqn();
89 io = calloc(opt.num_io_queues, sizeof(*io));
90 error = connect_nvm_queues(&aparams, trtype, adrfam, address, port,
91 cntlid, subnqn, hostnqn, opt.kato * 1000, &admin, io,
92 opt.num_io_queues, opt.queue_size, &cdata);
93 if (error != 0) {
94 free(io);
95 return (error);
96 }
97
98 if (dle == NULL) {
99 error = nvmf_init_dle_from_admin_qp(admin, &cdata, &dle_thunk);
100 if (error != 0) {
101 warnc(error, "Failed to generate handoff parameters");
102 disconnect_nvm_queues(admin, io, opt.num_io_queues);
103 free(io);
104 return (EX_IOERR);
105 }
106 dle = &dle_thunk;
107 }
108
109 error = nvmf_handoff_host(dle, hostnqn, admin, opt.num_io_queues, io,
110 &cdata);
111 if (error != 0) {
112 warnc(error, "Failed to handoff queues to kernel");
113 free(io);
114 return (EX_IOERR);
115 }
116 free(io);
117 return (0);
118 }
119
120 static void
connect_discovery_entry(struct nvme_discovery_log_entry * entry)121 connect_discovery_entry(struct nvme_discovery_log_entry *entry)
122 {
123 int adrfam;
124
125 switch (entry->trtype) {
126 case NVMF_TRTYPE_TCP:
127 switch (entry->adrfam) {
128 case NVMF_ADRFAM_IPV4:
129 adrfam = AF_INET;
130 break;
131 case NVMF_ADRFAM_IPV6:
132 adrfam = AF_INET6;
133 break;
134 default:
135 warnx("Skipping unsupported address family for %s",
136 entry->subnqn);
137 return;
138 }
139 switch (entry->tsas.tcp.sectype) {
140 case NVME_TCP_SECURITY_NONE:
141 break;
142 default:
143 warnx("Skipping unsupported TCP security type for %s",
144 entry->subnqn);
145 return;
146 }
147 break;
148 default:
149 warnx("Skipping unsupported transport %s for %s",
150 nvmf_transport_type(entry->trtype), entry->subnqn);
151 return;
152 }
153
154 /*
155 * XXX: Track portids and avoid duplicate connections for a
156 * given (subnqn,portid)?
157 */
158
159 /* XXX: Should this make use of entry->aqsz in some way? */
160 connect_nvm_controller(entry->trtype, adrfam, entry->traddr,
161 entry->trsvcid, entry->cntlid, entry->subnqn, entry);
162 }
163
164 static void
connect_discovery_log_page(struct nvmf_qpair * qp)165 connect_discovery_log_page(struct nvmf_qpair *qp)
166 {
167 struct nvme_discovery_log *log;
168 int error;
169
170 error = nvmf_host_fetch_discovery_log_page(qp, &log);
171 if (error != 0)
172 errc(EX_IOERR, error, "Failed to fetch discovery log page");
173
174 for (u_int i = 0; i < log->numrec; i++)
175 connect_discovery_entry(&log->entries[i]);
176 free(log);
177 }
178
179 static void
discover_controllers(enum nvmf_trtype trtype,const char * address,const char * port)180 discover_controllers(enum nvmf_trtype trtype, const char *address,
181 const char *port)
182 {
183 struct nvmf_qpair *qp;
184
185 qp = connect_discovery_adminq(trtype, address, port, opt.hostnqn);
186
187 connect_discovery_log_page(qp);
188
189 nvmf_free_qpair(qp);
190 }
191
192 static void
connect_fn(const struct cmd * f,int argc,char * argv[])193 connect_fn(const struct cmd *f, int argc, char *argv[])
194 {
195 enum nvmf_trtype trtype;
196 const char *address, *port;
197 char *tofree;
198 u_long cntlid;
199 int error;
200
201 if (arg_parse(argc, argv, f))
202 return;
203
204 if (opt.num_io_queues <= 0)
205 errx(EX_USAGE, "Invalid number of I/O queues");
206
207 if (strcasecmp(opt.transport, "tcp") == 0) {
208 trtype = NVMF_TRTYPE_TCP;
209 } else
210 errx(EX_USAGE, "Unsupported or invalid transport");
211
212 nvmf_parse_address(opt.address, &address, &port, &tofree);
213 if (port == NULL)
214 errx(EX_USAGE, "Explicit port required");
215
216 cntlid = nvmf_parse_cntlid(opt.cntlid);
217
218 error = connect_nvm_controller(trtype, AF_UNSPEC, address, port, cntlid,
219 opt.subnqn, NULL);
220 if (error != 0)
221 exit(error);
222
223 free(tofree);
224 }
225
226 static void
connect_all_fn(const struct cmd * f,int argc,char * argv[])227 connect_all_fn(const struct cmd *f, int argc, char *argv[])
228 {
229 enum nvmf_trtype trtype;
230 const char *address, *port;
231 char *tofree;
232
233 if (arg_parse(argc, argv, f))
234 return;
235
236 if (opt.num_io_queues <= 0)
237 errx(EX_USAGE, "Invalid number of I/O queues");
238
239 if (strcasecmp(opt.transport, "tcp") == 0) {
240 trtype = NVMF_TRTYPE_TCP;
241 } else
242 errx(EX_USAGE, "Unsupported or invalid transport");
243
244 nvmf_parse_address(opt.address, &address, &port, &tofree);
245 discover_controllers(trtype, address, port);
246
247 free(tofree);
248 }
249
250 static const struct opts connect_opts[] = {
251 #define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc }
252 OPT("transport", 't', arg_string, opt, transport,
253 "Transport type"),
254 OPT("cntlid", 'c', arg_string, opt, cntlid,
255 "Controller ID"),
256 OPT("nr-io-queues", 'i', arg_uint16, opt, num_io_queues,
257 "Number of I/O queues"),
258 OPT("queue-size", 'Q', arg_uint16, opt, queue_size,
259 "Number of entries in each I/O queue"),
260 OPT("keep-alive-tmo", 'k', arg_uint32, opt, kato,
261 "Keep Alive timeout (in seconds)"),
262 OPT("hostnqn", 'q', arg_string, opt, hostnqn,
263 "Host NQN"),
264 OPT("flow_control", 'F', arg_none, opt, flow_control,
265 "Request SQ flow control"),
266 OPT("hdr_digests", 'g', arg_none, opt, header_digests,
267 "Enable TCP PDU header digests"),
268 OPT("data_digests", 'G', arg_none, opt, data_digests,
269 "Enable TCP PDU data digests"),
270 { NULL, 0, arg_none, NULL, NULL }
271 };
272 #undef OPT
273
274 static const struct args connect_args[] = {
275 { arg_string, &opt.address, "address" },
276 { arg_string, &opt.subnqn, "SubNQN" },
277 { arg_none, NULL, NULL },
278 };
279
280 static const struct args connect_all_args[] = {
281 { arg_string, &opt.address, "address" },
282 { arg_none, NULL, NULL },
283 };
284
285 static struct cmd connect_cmd = {
286 .name = "connect",
287 .fn = connect_fn,
288 .descr = "Connect to a fabrics controller",
289 .ctx_size = sizeof(opt),
290 .opts = connect_opts,
291 .args = connect_args,
292 };
293
294 static struct cmd connect_all_cmd = {
295 .name = "connect-all",
296 .fn = connect_all_fn,
297 .descr = "Discover and connect to fabrics controllers",
298 .ctx_size = sizeof(opt),
299 .opts = connect_opts,
300 .args = connect_all_args,
301 };
302
303 CMD_COMMAND(connect_cmd);
304 CMD_COMMAND(connect_all_cmd);
305