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 <netinet/in.h>
10#include <arpa/inet.h>
11#include <assert.h>
12#include <err.h>
13#include <libnvmf.h>
14#include <pthread.h>
15#include <stdio.h>
16#include <stdlib.h>
17#include <string.h>
18#include <unistd.h>
19
20#include "internal.h"
21
22struct io_controller_data {
23	struct nvme_discovery_log_entry entry;
24	bool wildcard;
25};
26
27struct discovery_controller {
28	struct nvme_discovery_log *discovery_log;
29	size_t discovery_log_len;
30	int s;
31};
32
33struct discovery_thread_arg {
34	struct controller *c;
35	struct nvmf_qpair *qp;
36	int s;
37};
38
39static struct io_controller_data *io_controllers;
40static struct nvmf_association *discovery_na;
41static u_int num_io_controllers;
42
43static bool
44init_discovery_log_entry(struct nvme_discovery_log_entry *entry, int s,
45    const char *subnqn)
46{
47	struct sockaddr_storage ss;
48	socklen_t len;
49	bool wildcard;
50
51	len = sizeof(ss);
52	if (getsockname(s, (struct sockaddr *)&ss, &len) == -1)
53		err(1, "getsockname");
54
55	memset(entry, 0, sizeof(*entry));
56	entry->trtype = NVMF_TRTYPE_TCP;
57	switch (ss.ss_family) {
58	case AF_INET:
59	{
60		struct sockaddr_in *sin;
61
62		sin = (struct sockaddr_in *)&ss;
63		entry->adrfam = NVMF_ADRFAM_IPV4;
64		snprintf(entry->trsvcid, sizeof(entry->trsvcid), "%u",
65		    htons(sin->sin_port));
66		if (inet_ntop(AF_INET, &sin->sin_addr, entry->traddr,
67		    sizeof(entry->traddr)) == NULL)
68			err(1, "inet_ntop");
69		wildcard = (sin->sin_addr.s_addr == htonl(INADDR_ANY));
70		break;
71	}
72	case AF_INET6:
73	{
74		struct sockaddr_in6 *sin6;
75
76		sin6 = (struct sockaddr_in6 *)&ss;
77		entry->adrfam = NVMF_ADRFAM_IPV6;
78		snprintf(entry->trsvcid, sizeof(entry->trsvcid), "%u",
79		    htons(sin6->sin6_port));
80		if (inet_ntop(AF_INET6, &sin6->sin6_addr, entry->traddr,
81		    sizeof(entry->traddr)) == NULL)
82			err(1, "inet_ntop");
83		wildcard = (memcmp(&sin6->sin6_addr, &in6addr_any,
84		    sizeof(in6addr_any)) == 0);
85		break;
86	}
87	default:
88		errx(1, "Unsupported address family %u", ss.ss_family);
89	}
90	entry->subtype = NVMF_SUBTYPE_NVME;
91	if (flow_control_disable)
92		entry->treq |= (1 << 2);
93	entry->portid = htole16(1);
94	entry->cntlid = htole16(NVMF_CNTLID_DYNAMIC);
95	entry->aqsz = NVME_MAX_ADMIN_ENTRIES;
96	strlcpy(entry->subnqn, subnqn, sizeof(entry->subnqn));
97	return (wildcard);
98}
99
100void
101init_discovery(void)
102{
103	struct nvmf_association_params aparams;
104
105	memset(&aparams, 0, sizeof(aparams));
106	aparams.sq_flow_control = false;
107	aparams.dynamic_controller_model = true;
108	aparams.max_admin_qsize = NVME_MAX_ADMIN_ENTRIES;
109	aparams.tcp.pda = 0;
110	aparams.tcp.header_digests = header_digests;
111	aparams.tcp.data_digests = data_digests;
112	aparams.tcp.maxr2t = 1;
113	aparams.tcp.maxh2cdata = 256 * 1024;
114	discovery_na = nvmf_allocate_association(NVMF_TRTYPE_TCP, true,
115	    &aparams);
116	if (discovery_na == NULL)
117		err(1, "Failed to create discovery association");
118}
119
120void
121discovery_add_io_controller(int s, const char *subnqn)
122{
123	struct io_controller_data *icd;
124
125	io_controllers = reallocf(io_controllers, (num_io_controllers + 1) *
126	    sizeof(*io_controllers));
127
128	icd = &io_controllers[num_io_controllers];
129	num_io_controllers++;
130
131	icd->wildcard = init_discovery_log_entry(&icd->entry, s, subnqn);
132}
133
134static void
135build_discovery_log_page(struct discovery_controller *dc)
136{
137	struct sockaddr_storage ss;
138	socklen_t len;
139	char traddr[256];
140	u_int i, nentries;
141	uint8_t adrfam;
142
143	if (dc->discovery_log != NULL)
144		return;
145
146	len = sizeof(ss);
147	if (getsockname(dc->s, (struct sockaddr *)&ss, &len) == -1) {
148		warn("build_discovery_log_page: getsockname");
149		return;
150	}
151
152	memset(traddr, 0, sizeof(traddr));
153	switch (ss.ss_family) {
154	case AF_INET:
155	{
156		struct sockaddr_in *sin;
157
158		sin = (struct sockaddr_in *)&ss;
159		adrfam = NVMF_ADRFAM_IPV4;
160		if (inet_ntop(AF_INET, &sin->sin_addr, traddr,
161		    sizeof(traddr)) == NULL) {
162			warn("build_discovery_log_page: inet_ntop");
163			return;
164		}
165		break;
166	}
167	case AF_INET6:
168	{
169		struct sockaddr_in6 *sin6;
170
171		sin6 = (struct sockaddr_in6 *)&ss;
172		adrfam = NVMF_ADRFAM_IPV6;
173		if (inet_ntop(AF_INET6, &sin6->sin6_addr, traddr,
174		    sizeof(traddr)) == NULL) {
175			warn("build_discovery_log_page: inet_ntop");
176			return;
177		}
178		break;
179	}
180	default:
181		assert(false);
182	}
183
184	nentries = 0;
185	for (i = 0; i < num_io_controllers; i++) {
186		if (io_controllers[i].wildcard &&
187		    io_controllers[i].entry.adrfam != adrfam)
188			continue;
189		nentries++;
190	}
191
192	dc->discovery_log_len = sizeof(*dc->discovery_log) +
193	    nentries * sizeof(struct nvme_discovery_log_entry);
194	dc->discovery_log = calloc(dc->discovery_log_len, 1);
195	dc->discovery_log->numrec = nentries;
196	dc->discovery_log->recfmt = 0;
197	nentries = 0;
198	for (i = 0; i < num_io_controllers; i++) {
199		if (io_controllers[i].wildcard &&
200		    io_controllers[i].entry.adrfam != adrfam)
201			continue;
202
203		dc->discovery_log->entries[nentries] = io_controllers[i].entry;
204		if (io_controllers[i].wildcard)
205			memcpy(dc->discovery_log->entries[nentries].traddr,
206			    traddr, sizeof(traddr));
207	}
208}
209
210static void
211handle_get_log_page_command(const struct nvmf_capsule *nc,
212    const struct nvme_command *cmd, struct discovery_controller *dc)
213{
214	uint64_t offset;
215	uint32_t length;
216
217	switch (nvmf_get_log_page_id(cmd)) {
218	case NVME_LOG_DISCOVERY:
219		break;
220	default:
221		warnx("Unsupported log page %u for discovery controller",
222		    nvmf_get_log_page_id(cmd));
223		goto error;
224	}
225
226	build_discovery_log_page(dc);
227
228	offset = nvmf_get_log_page_offset(cmd);
229	if (offset >= dc->discovery_log_len)
230		goto error;
231
232	length = nvmf_get_log_page_length(cmd);
233	if (length > dc->discovery_log_len - offset)
234		length = dc->discovery_log_len - offset;
235
236	nvmf_send_controller_data(nc, (char *)dc->discovery_log + offset,
237	    length);
238	return;
239error:
240	nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD);
241}
242
243static bool
244discovery_command(const struct nvmf_capsule *nc, const struct nvme_command *cmd,
245    void *arg)
246{
247	struct discovery_controller *dc = arg;
248
249	switch (cmd->opc) {
250	case NVME_OPC_GET_LOG_PAGE:
251		handle_get_log_page_command(nc, cmd, dc);
252		return (true);
253	default:
254		return (false);
255	}
256}
257
258static void *
259discovery_thread(void *arg)
260{
261	struct discovery_thread_arg *dta = arg;
262	struct discovery_controller dc;
263
264	pthread_detach(pthread_self());
265
266	memset(&dc, 0, sizeof(dc));
267	dc.s = dta->s;
268
269	controller_handle_admin_commands(dta->c, discovery_command, &dc);
270
271	free(dc.discovery_log);
272	free_controller(dta->c);
273
274	nvmf_free_qpair(dta->qp);
275
276	close(dta->s);
277	free(dta);
278	return (NULL);
279}
280
281void
282handle_discovery_socket(int s)
283{
284	struct nvmf_fabric_connect_data data;
285	struct nvme_controller_data cdata;
286	struct nvmf_qpair_params qparams;
287	struct discovery_thread_arg *dta;
288	struct nvmf_capsule *nc;
289	struct nvmf_qpair *qp;
290	pthread_t thr;
291	int error;
292
293	memset(&qparams, 0, sizeof(qparams));
294	qparams.tcp.fd = s;
295
296	nc = NULL;
297	qp = nvmf_accept(discovery_na, &qparams, &nc, &data);
298	if (qp == NULL) {
299		warnx("Failed to create discovery qpair: %s",
300		    nvmf_association_error(discovery_na));
301		goto error;
302	}
303
304	if (strcmp(data.subnqn, NVMF_DISCOVERY_NQN) != 0) {
305		warn("Discovery qpair with invalid SubNQN: %.*s",
306		    (int)sizeof(data.subnqn), data.subnqn);
307		nvmf_connect_invalid_parameters(nc, true,
308		    offsetof(struct nvmf_fabric_connect_data, subnqn));
309		goto error;
310	}
311
312	/* Just use a controller ID of 1 for all discovery controllers. */
313	error = nvmf_finish_accept(nc, 1);
314	if (error != 0) {
315		warnc(error, "Failed to send CONNECT reponse");
316		goto error;
317	}
318
319	nvmf_init_discovery_controller_data(qp, &cdata);
320
321	dta = malloc(sizeof(*dta));
322	dta->qp = qp;
323	dta->s = s;
324	dta->c = init_controller(qp, &cdata);
325
326	error = pthread_create(&thr, NULL, discovery_thread, dta);
327	if (error != 0) {
328		warnc(error, "Failed to create discovery thread");
329		free_controller(dta->c);
330		free(dta);
331		goto error;
332	}
333
334	nvmf_free_capsule(nc);
335	return;
336
337error:
338	if (nc != NULL)
339		nvmf_free_capsule(nc);
340	if (qp != NULL)
341		nvmf_free_qpair(qp);
342	close(s);
343}
344