xref: /freebsd/sbin/nvmecontrol/reconnect.c (revision 8bba2c0f8958443790b1f3abc0675719da987e87)
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/dnv.h>
9 #include <sys/nv.h>
10 #include <sys/socket.h>
11 #include <err.h>
12 #include <libnvmf.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <sysexits.h>
16 #include <unistd.h>
17 
18 #include "nvmecontrol.h"
19 #include "fabrics.h"
20 
21 /*
22  * See comment about other possible settings in connect.c.
23  */
24 
25 static struct options {
26 	const char	*dev;
27 	const char	*transport;
28 	const char	*hostnqn;
29 	uint32_t	kato;
30 	uint16_t	num_io_queues;
31 	uint16_t	queue_size;
32 	bool		data_digests;
33 	bool		flow_control;
34 	bool		header_digests;
35 } opt = {
36 	.dev = NULL,
37 	.transport = "tcp",
38 	.hostnqn = NULL,
39 	.kato = NVMF_KATO_DEFAULT / 1000,
40 	.num_io_queues = 1,
41 	.queue_size = 0,
42 	.data_digests = false,
43 	.flow_control = false,
44 	.header_digests = false,
45 };
46 
47 static void
tcp_association_params(struct nvmf_association_params * params,bool header_digests,bool data_digests)48 tcp_association_params(struct nvmf_association_params *params,
49     bool header_digests, bool data_digests)
50 {
51 	params->tcp.pda = 0;
52 	params->tcp.header_digests = header_digests;
53 	params->tcp.data_digests = data_digests;
54 	/* XXX */
55 	params->tcp.maxr2t = 1;
56 }
57 
58 static int
reconnect_nvm_controller(int fd,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,u_int num_io_queues,u_int queue_size,const struct nvme_discovery_log_entry * dle)59 reconnect_nvm_controller(int fd, const struct nvmf_association_params *aparams,
60     enum nvmf_trtype trtype, int adrfam, const char *address, const char *port,
61     uint16_t cntlid, const char *subnqn, const char *hostnqn, uint32_t kato,
62     u_int num_io_queues, u_int queue_size,
63     const struct nvme_discovery_log_entry *dle)
64 {
65 	struct nvme_controller_data cdata;
66 	struct nvme_discovery_log_entry dle_thunk;
67 	struct nvmf_qpair *admin, **io;
68 	int error;
69 
70 	io = calloc(num_io_queues, sizeof(*io));
71 	error = connect_nvm_queues(aparams, trtype, adrfam, address, port,
72 	    cntlid, subnqn, hostnqn, kato, &admin, io, num_io_queues,
73 	    queue_size, &cdata);
74 	if (error != 0) {
75 		free(io);
76 		return (error);
77 	}
78 
79 	if (dle == NULL) {
80 		error = nvmf_init_dle_from_admin_qp(admin, &cdata, &dle_thunk);
81 		if (error != 0) {
82 			warnc(error, "Failed to generate handoff parameters");
83 			disconnect_nvm_queues(admin, io, num_io_queues);
84 			free(io);
85 			return (EX_IOERR);
86 		}
87 		dle = &dle_thunk;
88 	}
89 
90 	error = nvmf_reconnect_host(fd, dle, hostnqn, admin, num_io_queues, io,
91 	    &cdata);
92 	if (error != 0) {
93 		warnc(error, "Failed to handoff queues to kernel");
94 		free(io);
95 		return (EX_IOERR);
96 	}
97 	free(io);
98 	return (0);
99 }
100 
101 static int
reconnect_by_address(int fd,const nvlist_t * rparams,const char * addr)102 reconnect_by_address(int fd, const nvlist_t *rparams, const char *addr)
103 {
104 	const struct nvme_discovery_log_entry *dle;
105 	struct nvmf_association_params aparams;
106 	enum nvmf_trtype trtype;
107 	const char *address, *hostnqn, *port;
108 	char *subnqn, *tofree;
109 	int error;
110 
111 	memset(&aparams, 0, sizeof(aparams));
112 	aparams.sq_flow_control = opt.flow_control;
113 	if (strcasecmp(opt.transport, "tcp") == 0) {
114 		trtype = NVMF_TRTYPE_TCP;
115 		tcp_association_params(&aparams, opt.header_digests,
116 		    opt.data_digests);
117 	} else {
118 		warnx("Unsupported or invalid transport");
119 		return (EX_USAGE);
120 	}
121 
122 	nvmf_parse_address(addr, &address, &port, &tofree);
123 	if (port == NULL) {
124 		free(tofree);
125 		warnx("Explicit port required");
126 		return (EX_USAGE);
127 	}
128 
129 	dle = nvlist_get_binary(rparams, "dle", NULL);
130 
131 	hostnqn = opt.hostnqn;
132 	if (hostnqn == NULL)
133 		hostnqn = nvmf_default_hostnqn();
134 
135 	/* Ensure subnqn is a terminated C string. */
136 	subnqn = strndup(dle->subnqn, sizeof(dle->subnqn));
137 
138 	error = reconnect_nvm_controller(fd, &aparams, trtype, AF_UNSPEC,
139 	    address, port, le16toh(dle->cntlid), subnqn, hostnqn,
140 	    opt.kato * 1000, opt.num_io_queues, opt.queue_size, NULL);
141 	free(subnqn);
142 	free(tofree);
143 	return (error);
144 }
145 
146 static int
reconnect_by_params(int fd,const nvlist_t * rparams)147 reconnect_by_params(int fd, const nvlist_t *rparams)
148 {
149 	struct nvmf_association_params aparams;
150 	const struct nvme_discovery_log_entry *dle;
151 	char *address, *port, *subnqn;
152 	int adrfam, error;
153 
154 	dle = nvlist_get_binary(rparams, "dle", NULL);
155 
156 	memset(&aparams, 0, sizeof(aparams));
157 	aparams.sq_flow_control = nvlist_get_bool(rparams, "sq_flow_control");
158 	switch (dle->trtype) {
159 	case NVMF_TRTYPE_TCP:
160 		switch (dle->adrfam) {
161 		case NVMF_ADRFAM_IPV4:
162 			adrfam = AF_INET;
163 			break;
164 		case NVMF_ADRFAM_IPV6:
165 			adrfam = AF_INET6;
166 			break;
167 		default:
168 			warnx("Unsupported address family");
169 			return (EX_UNAVAILABLE);
170 		}
171 		switch (dle->tsas.tcp.sectype) {
172 		case NVME_TCP_SECURITY_NONE:
173 			break;
174 		default:
175 			warnx("Unsupported TCP security type");
176 			return (EX_UNAVAILABLE);
177 		}
178 		break;
179 
180 		tcp_association_params(&aparams,
181 		    nvlist_get_bool(rparams, "header_digests"),
182 		    nvlist_get_bool(rparams, "data_digests"));
183 		break;
184 	default:
185 		warnx("Unsupported transport %s",
186 		    nvmf_transport_type(dle->trtype));
187 		return (EX_UNAVAILABLE);
188 	}
189 
190 	/* Ensure address, port, and subnqn is a terminated C string. */
191 	address = strndup(dle->traddr, sizeof(dle->traddr));
192 	port = strndup(dle->trsvcid, sizeof(dle->trsvcid));
193 	subnqn = strndup(dle->subnqn, sizeof(dle->subnqn));
194 
195 	error = reconnect_nvm_controller(fd, &aparams, dle->trtype, adrfam,
196 	    address, port, le16toh(dle->cntlid), dle->subnqn,
197 	    nvlist_get_string(rparams, "hostnqn"),
198 	    dnvlist_get_number(rparams, "kato", 0),
199 	    nvlist_get_number(rparams, "num_io_queues"),
200 	    nvlist_get_number(rparams, "io_qsize"), dle);
201 	free(subnqn);
202 	free(port);
203 	free(address);
204 	return (error);
205 }
206 
207 static int
fetch_and_validate_rparams(int fd,nvlist_t ** rparamsp)208 fetch_and_validate_rparams(int fd, nvlist_t **rparamsp)
209 {
210 	const struct nvme_discovery_log_entry *dle;
211 	nvlist_t *rparams;
212 	size_t len;
213 	int error;
214 
215 	error = nvmf_reconnect_params(fd, &rparams);
216 	if (error != 0) {
217 		warnc(error, "Failed to fetch reconnect parameters");
218 		return (EX_IOERR);
219 	}
220 
221 	if (!nvlist_exists_binary(rparams, "dle") ||
222 	    !nvlist_exists_string(rparams, "hostnqn") ||
223 	    !nvlist_exists_number(rparams, "num_io_queues") ||
224 	    !nvlist_exists_number(rparams, "io_qsize") ||
225 	    !nvlist_exists_bool(rparams, "sq_flow_control")) {
226 		nvlist_destroy(rparams);
227 		warnx("Missing required reconnect parameters");
228 		return (EX_IOERR);
229 	}
230 
231 	dle = nvlist_get_binary(rparams, "dle", &len);
232 	if (len != sizeof(*dle)) {
233 		nvlist_destroy(rparams);
234 		warnx("Discovery Log entry reconnect parameter is wrong size");
235 		return (EX_IOERR);
236 	}
237 
238 	switch (dle->trtype) {
239 	case NVMF_TRTYPE_TCP:
240 		if (!nvlist_exists_bool(rparams, "header_digests") ||
241 		    !nvlist_exists_bool(rparams, "data_digests")) {
242 			nvlist_destroy(rparams);
243 			warnx("Missing required reconnect parameters");
244 			return (EX_IOERR);
245 		}
246 		break;
247 	default:
248 		nvlist_destroy(rparams);
249 		warnx("Unsupported transport %s",
250 		    nvmf_transport_type(dle->trtype));
251 		return (EX_UNAVAILABLE);
252 	}
253 
254 	*rparamsp = rparams;
255 	return (0);
256 }
257 
258 static void
reconnect_fn(const struct cmd * f,int argc,char * argv[])259 reconnect_fn(const struct cmd *f, int argc, char *argv[])
260 {
261 	nvlist_t *rparams;
262 	int error, fd;
263 
264 	if (arg_parse(argc, argv, f))
265 		return;
266 
267 	open_dev(opt.dev, &fd, 1, 1);
268 	error = fetch_and_validate_rparams(fd, &rparams);
269 	if (error != 0)
270 		exit(error);
271 
272 	/* Check for optional address. */
273 	if (optind < argc)
274 		error = reconnect_by_address(fd, rparams, argv[optind]);
275 	else
276 		error = reconnect_by_params(fd, rparams);
277 	if (error != 0)
278 		exit(error);
279 
280 	nvlist_destroy(rparams);
281 	close(fd);
282 }
283 
284 static const struct opts reconnect_opts[] = {
285 #define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc }
286 	OPT("transport", 't', arg_string, opt, transport,
287 	    "Transport type"),
288 	OPT("nr-io-queues", 'i', arg_uint16, opt, num_io_queues,
289 	    "Number of I/O queues"),
290 	OPT("queue-size", 'Q', arg_uint16, opt, queue_size,
291 	    "Number of entries in each I/O queue"),
292 	OPT("keep-alive-tmo", 'k', arg_uint32, opt, kato,
293 	    "Keep Alive timeout (in seconds)"),
294 	OPT("hostnqn", 'q', arg_string, opt, hostnqn,
295 	    "Host NQN"),
296 	OPT("flow_control", 'F', arg_none, opt, flow_control,
297 	    "Request SQ flow control"),
298 	OPT("hdr_digests", 'g', arg_none, opt, header_digests,
299 	    "Enable TCP PDU header digests"),
300 	OPT("data_digests", 'G', arg_none, opt, data_digests,
301 	    "Enable TCP PDU data digests"),
302 	{ NULL, 0, arg_none, NULL, NULL }
303 };
304 #undef OPT
305 
306 static const struct args reconnect_args[] = {
307 	{ arg_string, &opt.dev, "controller-id" },
308 	{ arg_none, NULL, NULL },
309 };
310 
311 static struct cmd reconnect_cmd = {
312 	.name = "reconnect",
313 	.fn = reconnect_fn,
314 	.descr = "Reconnect to a fabrics controller",
315 	.ctx_size = sizeof(opt),
316 	.opts = reconnect_opts,
317 	.args = reconnect_args,
318 };
319 
320 CMD_COMMAND(reconnect_cmd);
321