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