1188628Simp/*-
2204977Simp * SPDX-License-Identifier: BSD-2-Clause
3188628Simp *
4188628Simp * Copyright (c) 2023-2024 Chelsio Communications, Inc.
5188628Simp * Written by: John Baldwin <jhb@FreeBSD.org>
6188628Simp */
7188628Simp
8188628Simp#include <sys/socket.h>
9188628Simp#include <err.h>
10188628Simp#include <libnvmf.h>
11188628Simp#include <stdlib.h>
12188628Simp#include <string.h>
13188628Simp#include <sysexits.h>
14188628Simp#include <unistd.h>
15188628Simp
16188628Simp#include "comnd.h"
17188628Simp#include "fabrics.h"
18188628Simp
19188628Simp/*
20188628Simp * Settings that are currently hardcoded but could be exposed to the
21188628Simp * user via additional command line options:
22188628Simp *
23188628Simp * - ADMIN queue entries
24188628Simp * - MaxR2T
25188628Simp */
26188628Simp
27188628Simpstatic struct options {
28188628Simp	const char	*transport;
29188628Simp	const char	*address;
30188628Simp	const char	*cntlid;
31188628Simp	const char	*subnqn;
32288424Sjhb	const char	*hostnqn;
33288424Sjhb	uint32_t	kato;
34188628Simp	uint16_t	num_io_queues;
35288424Sjhb	uint16_t	queue_size;
36188628Simp	bool		data_digests;
37188628Simp	bool		flow_control;
38188628Simp	bool		header_digests;
39188628Simp} opt = {
40188628Simp	.transport = "tcp",
41188628Simp	.address = NULL,
42188628Simp	.cntlid = "dynamic",
43188628Simp	.subnqn = NULL,
44294849Sjhb	.hostnqn = NULL,
45188628Simp	.kato = NVMF_KATO_DEFAULT / 1000,
46188628Simp	.num_io_queues = 1,
47188628Simp	.queue_size = 0,
48288424Sjhb	.data_digests = false,
49288424Sjhb	.flow_control = false,
50240562Szont	.header_digests = false,
51240005Szont};
52240005Szont
53288424Sjhbstatic void
54240562Szonttcp_association_params(struct nvmf_association_params *params)
55288424Sjhb{
56188628Simp	params->tcp.pda = 0;
57240562Szont	params->tcp.header_digests = opt.header_digests;
58288424Sjhb	params->tcp.data_digests = opt.data_digests;
59240562Szont	/* XXX */
60240005Szont	params->tcp.maxr2t = 1;
61288424Sjhb}
62240005Szont
63188628Simpstatic int
64288424Sjhbconnect_nvm_controller(enum nvmf_trtype trtype, int adrfam, const char *address,
65288424Sjhb    const char *port, uint16_t cntlid, const char *subnqn)
66288424Sjhb{
67288424Sjhb	struct nvme_controller_data cdata;
68288424Sjhb	struct nvmf_association_params aparams;
69288424Sjhb	struct nvmf_qpair *admin, **io;
70288424Sjhb	int error;
71288424Sjhb
72288424Sjhb	memset(&aparams, 0, sizeof(aparams));
73288424Sjhb	aparams.sq_flow_control = opt.flow_control;
74288424Sjhb	switch (trtype) {
75288424Sjhb	case NVMF_TRTYPE_TCP:
76288424Sjhb		tcp_association_params(&aparams);
77288424Sjhb		break;
78288424Sjhb	default:
79288424Sjhb		warnx("Unsupported transport %s", nvmf_transport_type(trtype));
80288424Sjhb		return (EX_UNAVAILABLE);
81288424Sjhb	}
82288424Sjhb
83288424Sjhb	io = calloc(opt.num_io_queues, sizeof(*io));
84240005Szont	error = connect_nvm_queues(&aparams, trtype, adrfam, address, port,
85188628Simp	    cntlid, subnqn, opt.hostnqn, opt.kato, &admin, io,
86288424Sjhb	    opt.num_io_queues, opt.queue_size, &cdata);
87288424Sjhb	if (error != 0) {
88188628Simp		free(io);
89288424Sjhb		return (error);
90188628Simp	}
91188628Simp
92288424Sjhb	error = nvmf_handoff_host(admin, opt.num_io_queues, io, &cdata);
93288424Sjhb	if (error != 0) {
94288424Sjhb		warnc(error, "Failed to handoff queues to kernel");
95240005Szont		free(io);
96288424Sjhb		return (EX_IOERR);
97288424Sjhb	}
98288424Sjhb	free(io);
99288424Sjhb	return (0);
100240562Szont}
101240005Szont
102288424Sjhbstatic void
103240005Szontconnect_discovery_entry(struct nvme_discovery_log_entry *entry)
104188628Simp{
105288424Sjhb	int adrfam;
106188628Simp
107188628Simp	switch (entry->trtype) {
108288424Sjhb	case NVMF_TRTYPE_TCP:
109288424Sjhb		switch (entry->adrfam) {
110240005Szont		case NVMF_ADRFAM_IPV4:
111240005Szont			adrfam = AF_INET;
112240562Szont			break;
113188628Simp		case NVMF_ADRFAM_IPV6:
114240562Szont			adrfam = AF_INET6;
115240562Szont			break;
116288424Sjhb		default:
117240005Szont			warnx("Skipping unsupported address family for %s",
118240005Szont			    entry->subnqn);
119188628Simp			return;
120288424Sjhb		}
121288424Sjhb		switch (entry->tsas.tcp.sectype) {
122288424Sjhb		case NVME_TCP_SECURITY_NONE:
123288424Sjhb			break;
124288424Sjhb		default:
125288424Sjhb			warnx("Skipping unsupported TCP security type for %s",
126240005Szont			    entry->subnqn);
127188628Simp			return;
128289239Sbdrewery		}
129288424Sjhb		break;
130288424Sjhb	default:
131288424Sjhb		warnx("Skipping unsupported transport %s for %s",
132288424Sjhb		    nvmf_transport_type(entry->trtype), entry->subnqn);
133288424Sjhb		return;
134295056Sjhb	}
135288424Sjhb
136288424Sjhb	/*
137288424Sjhb	 * XXX: Track portids and avoid duplicate connections for a
138286938Sjhb	 * given (subnqn,portid)?
139289239Sbdrewery	 */
140
141	/* XXX: Should this make use of entry->aqsz in some way? */
142	connect_nvm_controller(entry->trtype, adrfam, entry->traddr,
143	    entry->trsvcid, entry->cntlid, entry->subnqn);
144}
145
146static void
147connect_discovery_log_page(struct nvmf_qpair *qp)
148{
149	struct nvme_discovery_log *log;
150	int error;
151
152	error = nvmf_host_fetch_discovery_log_page(qp, &log);
153	if (error != 0)
154		errc(EX_IOERR, error, "Failed to fetch discovery log page");
155
156	for (u_int i = 0; i < log->numrec; i++)
157		connect_discovery_entry(&log->entries[i]);
158	free(log);
159}
160
161static void
162discover_controllers(enum nvmf_trtype trtype, const char *address,
163    const char *port)
164{
165	struct nvmf_qpair *qp;
166
167	qp = connect_discovery_adminq(trtype, address, port, opt.hostnqn);
168
169	connect_discovery_log_page(qp);
170
171	nvmf_free_qpair(qp);
172}
173
174static void
175connect_fn(const struct cmd *f, int argc, char *argv[])
176{
177	enum nvmf_trtype trtype;
178	const char *address, *port;
179	char *tofree;
180	u_long cntlid;
181	int error;
182
183	if (arg_parse(argc, argv, f))
184		return;
185
186	if (opt.num_io_queues <= 0)
187		errx(EX_USAGE, "Invalid number of I/O queues");
188
189	if (strcasecmp(opt.transport, "tcp") == 0) {
190		trtype = NVMF_TRTYPE_TCP;
191	} else
192		errx(EX_USAGE, "Unsupported or invalid transport");
193
194	nvmf_parse_address(opt.address, &address, &port, &tofree);
195	if (port == NULL)
196		errx(EX_USAGE, "Explicit port required");
197
198	cntlid = nvmf_parse_cntlid(opt.cntlid);
199
200	error = connect_nvm_controller(trtype, AF_UNSPEC, address, port, cntlid,
201	    opt.subnqn);
202	if (error != 0)
203		exit(error);
204
205	free(tofree);
206}
207
208static void
209connect_all_fn(const struct cmd *f, int argc, char *argv[])
210{
211	enum nvmf_trtype trtype;
212	const char *address, *port;
213	char *tofree;
214
215	if (arg_parse(argc, argv, f))
216		return;
217
218	if (opt.num_io_queues <= 0)
219		errx(EX_USAGE, "Invalid number of I/O queues");
220
221	if (strcasecmp(opt.transport, "tcp") == 0) {
222		trtype = NVMF_TRTYPE_TCP;
223	} else
224		errx(EX_USAGE, "Unsupported or invalid transport");
225
226	nvmf_parse_address(opt.address, &address, &port, &tofree);
227	discover_controllers(trtype, address, port);
228
229	free(tofree);
230}
231
232static const struct opts connect_opts[] = {
233#define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc }
234	OPT("transport", 't', arg_string, opt, transport,
235	    "Transport type"),
236	OPT("cntlid", 'c', arg_string, opt, cntlid,
237	    "Controller ID"),
238	OPT("nr-io-queues", 'i', arg_uint16, opt, num_io_queues,
239	    "Number of I/O queues"),
240	OPT("queue-size", 'Q', arg_uint16, opt, queue_size,
241	    "Number of entries in each I/O queue"),
242	OPT("keep-alive-tmo", 'k', arg_uint32, opt, kato,
243	    "Keep Alive timeout (in seconds)"),
244	OPT("hostnqn", 'q', arg_string, opt, hostnqn,
245	    "Host NQN"),
246	OPT("flow_control", 'F', arg_none, opt, flow_control,
247	    "Request SQ flow control"),
248	OPT("hdr_digests", 'g', arg_none, opt, header_digests,
249	    "Enable TCP PDU header digests"),
250	OPT("data_digests", 'G', arg_none, opt, data_digests,
251	    "Enable TCP PDU data digests"),
252	{ NULL, 0, arg_none, NULL, NULL }
253};
254#undef OPT
255
256static const struct args connect_args[] = {
257	{ arg_string, &opt.address, "address" },
258	{ arg_string, &opt.subnqn, "SubNQN" },
259	{ arg_none, NULL, NULL },
260};
261
262static const struct args connect_all_args[] = {
263	{ arg_string, &opt.address, "address" },
264	{ arg_none, NULL, NULL },
265};
266
267static struct cmd connect_cmd = {
268	.name = "connect",
269	.fn = connect_fn,
270	.descr = "Connect to a fabrics controller",
271	.ctx_size = sizeof(opt),
272	.opts = connect_opts,
273	.args = connect_args,
274};
275
276static struct cmd connect_all_cmd = {
277	.name = "connect-all",
278	.fn = connect_all_fn,
279	.descr = "Discover and connect to fabrics controllers",
280	.ctx_size = sizeof(opt),
281	.opts = connect_opts,
282	.args = connect_all_args,
283};
284
285CMD_COMMAND(connect_cmd);
286CMD_COMMAND(connect_all_cmd);
287