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