xref: /freebsd/usr.sbin/ctld/nvmf_discovery.cc (revision d21b513988b7d685edf2ad77886b3e3c95cc0df8)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2023-2025 Chelsio Communications, Inc.
5  * Written by: John Baldwin <jhb@FreeBSD.org>
6  */
7 
8 #include <assert.h>
9 #include <errno.h>
10 #include <netdb.h>
11 #include <libiscsiutil.h>
12 #include <libnvmf.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <unistd.h>
16 #include <netinet/in.h>
17 
18 #include "ctld.hh"
19 #include "nvmf.hh"
20 
21 struct discovery_log {
22 	discovery_log(const struct portal_group *pg);
23 
datadiscovery_log24 	const char *data() const { return buf.data(); }
lengthdiscovery_log25 	size_t length() const { return buf.size(); }
26 
27 	void append(const struct nvme_discovery_log_entry *entry);
28 
29 private:
headerdiscovery_log30 	struct nvme_discovery_log *header()
31 	{ return reinterpret_cast<struct nvme_discovery_log *>(buf.data()); }
32 
33 	std::vector<char> buf;
34 };
35 
36 struct discovery_controller {
37 	discovery_controller(freebsd::fd_up s, struct nvmf_qpair *qp,
38 	    const discovery_log &discovery_log);
39 
40 	void handle_admin_commands();
41 private:
42 	bool update_cc(uint32_t new_cc);
43 	void handle_property_get(const struct nvmf_capsule *nc,
44 	    const struct nvmf_fabric_prop_get_cmd *pget);
45 	void handle_property_set(const struct nvmf_capsule *nc,
46 	    const struct nvmf_fabric_prop_set_cmd *pset);
47 	void handle_fabrics_command(const struct nvmf_capsule *nc,
48 	    const struct nvmf_fabric_cmd *cmd);
49 	void handle_identify_command(const struct nvmf_capsule *nc,
50 	    const struct nvme_command *cmd);
51 	void handle_get_log_page_command(const struct nvmf_capsule *nc,
52 	    const struct nvme_command *cmd);
53 
54 	struct nvmf_qpair *qp;
55 
56 	uint64_t cap = 0;
57 	uint32_t vs = 0;
58 	uint32_t cc = 0;
59 	uint32_t csts = 0;
60 
61 	bool shutdown = false;
62 
63 	struct nvme_controller_data cdata;
64 
65 	const struct discovery_log &discovery_log;
66 	freebsd::fd_up s;
67 };
68 
discovery_log(const struct portal_group * pg)69 discovery_log::discovery_log(const struct portal_group *pg) :
70 	buf(sizeof(nvme_discovery_log))
71 {
72 	struct nvme_discovery_log *log = header();
73 
74 	log->genctr = htole32(pg->conf()->genctr());
75 	log->recfmt = 0;
76 }
77 
78 void
append(const struct nvme_discovery_log_entry * entry)79 discovery_log::append(const struct nvme_discovery_log_entry *entry)
80 {
81 	const char *cp = reinterpret_cast<const char *>(entry);
82 	buf.insert(buf.end(), cp, cp + sizeof(*entry));
83 
84 	struct nvme_discovery_log *log = header();
85 	log->numrec = htole32(le32toh(log->numrec) + 1);
86 }
87 
88 static bool
discovery_controller_filtered(const struct portal_group * pg,const struct sockaddr * client_sa,std::string_view hostnqn,const struct port * port)89 discovery_controller_filtered(const struct portal_group *pg,
90     const struct sockaddr *client_sa, std::string_view hostnqn,
91     const struct port *port)
92 {
93 	const struct target *targ = port->target();
94 	const struct auth_group *ag = port->auth_group();
95 	if (ag == nullptr)
96 		ag = targ->auth_group();
97 
98 	assert(pg->discovery_filter() != discovery_filter::UNKNOWN);
99 
100 	if (pg->discovery_filter() >= discovery_filter::PORTAL &&
101 	    !ag->host_permitted(client_sa)) {
102 		log_debugx("host address does not match addresses "
103 		    "allowed for controller \"%s\"; skipping", targ->name());
104 		return true;
105 	}
106 
107 	if (pg->discovery_filter() >= discovery_filter::PORTAL_NAME &&
108 	    !ag->host_permitted(hostnqn) != 0) {
109 		log_debugx("HostNQN does not match NQNs "
110 		    "allowed for controller \"%s\"; skipping", targ->name());
111 		return true;
112 	}
113 
114 	/* XXX: auth not yet implemented for NVMe */
115 
116 	return false;
117 }
118 
119 static bool
portal_uses_wildcard_address(const struct portal * p)120 portal_uses_wildcard_address(const struct portal *p)
121 {
122 	const struct addrinfo *ai = p->ai();
123 
124 	switch (ai->ai_family) {
125 	case AF_INET:
126 	{
127 		const struct sockaddr_in *sin;
128 
129 		sin = (const struct sockaddr_in *)ai->ai_addr;
130 		return sin->sin_addr.s_addr == htonl(INADDR_ANY);
131 	}
132 	case AF_INET6:
133 	{
134 		const struct sockaddr_in6 *sin6;
135 
136 		sin6 = (const struct sockaddr_in6 *)ai->ai_addr;
137 		return memcmp(&sin6->sin6_addr, &in6addr_any,
138 		    sizeof(in6addr_any)) == 0;
139 	}
140 	default:
141 		__assert_unreachable();
142 	}
143 }
144 
145 static bool
init_discovery_log_entry(struct nvme_discovery_log_entry * entry,const struct target * target,const struct portal * portal,const char * wildcard_host)146 init_discovery_log_entry(struct nvme_discovery_log_entry *entry,
147     const struct target *target, const struct portal *portal,
148     const char *wildcard_host)
149 {
150 	/*
151 	 * The TCP port for I/O controllers might not be fixed, so
152 	 * fetch the sockaddr of the socket to determine which port
153 	 * the kernel chose.
154 	 */
155 	struct sockaddr_storage ss;
156 	socklen_t len = sizeof(ss);
157 	if (getsockname(portal->socket(), (struct sockaddr *)&ss, &len) == -1) {
158 		log_warn("Failed getsockname building discovery log entry");
159 		return false;
160 	}
161 
162 	const struct nvmf_association_params *aparams =
163 	    static_cast<const nvmf_portal *>(portal)->aparams();
164 
165 	memset(entry, 0, sizeof(*entry));
166 	entry->trtype = NVMF_TRTYPE_TCP;
167 	int error = getnameinfo((struct sockaddr *)&ss, len,
168 	    (char *)entry->traddr, sizeof(entry->traddr),
169 	    (char *)entry->trsvcid, sizeof(entry->trsvcid),
170 	    NI_NUMERICHOST | NI_NUMERICSERV);
171 	if (error != 0) {
172 		log_warnx("Failed getnameinfo building discovery log entry: %s",
173 		    gai_strerror(error));
174 		return false;
175 	}
176 
177 	if (portal_uses_wildcard_address(portal))
178 		strncpy((char *)entry->traddr, wildcard_host,
179 		    sizeof(entry->traddr));
180 	switch (portal->ai()->ai_family) {
181 	case AF_INET:
182 		entry->adrfam = NVMF_ADRFAM_IPV4;
183 		break;
184 	case AF_INET6:
185 		entry->adrfam = NVMF_ADRFAM_IPV6;
186 		break;
187 	default:
188 		__assert_unreachable();
189 	}
190 	entry->subtype = NVMF_SUBTYPE_NVME;
191 	if (!aparams->sq_flow_control)
192 		entry->treq |= (1 << 2);
193 	entry->portid = htole16(portal->portal_group()->tag());
194 	entry->cntlid = htole16(NVMF_CNTLID_DYNAMIC);
195 	entry->aqsz = aparams->max_admin_qsize;
196 	strncpy((char *)entry->subnqn, target->name(), sizeof(entry->subnqn));
197 	return true;
198 }
199 
200 static discovery_log
build_discovery_log_page(const struct portal_group * pg,int fd,const struct sockaddr * client_sa,const struct nvmf_fabric_connect_data & data)201 build_discovery_log_page(const struct portal_group *pg, int fd,
202     const struct sockaddr *client_sa,
203     const struct nvmf_fabric_connect_data &data)
204 {
205 	discovery_log discovery_log(pg);
206 
207 	struct sockaddr_storage ss;
208 	socklen_t len = sizeof(ss);
209 	if (getsockname(fd, (struct sockaddr *)&ss, &len) == -1) {
210 		log_warn("build_discovery_log_page: getsockname");
211 		return discovery_log;
212 	}
213 
214 	char wildcard_host[NI_MAXHOST];
215 	int error = getnameinfo((struct sockaddr *)&ss, len, wildcard_host,
216 	    sizeof(wildcard_host), NULL, 0, NI_NUMERICHOST);
217 	if (error != 0) {
218 		log_warnx("build_discovery_log_page: getnameinfo: %s",
219 		    gai_strerror(error));
220 		return discovery_log;
221 	}
222 
223 	const char *nqn = (const char *)data.hostnqn;
224 	std::string hostnqn(nqn, strnlen(nqn, sizeof(data.hostnqn)));
225 	for (const auto &kv : pg->ports()) {
226 		const struct port *port = kv.second;
227 		if (discovery_controller_filtered(pg, client_sa, hostnqn, port))
228 			continue;
229 
230 		for (const portal_up &portal : pg->portals()) {
231 			if (portal->protocol() != portal_protocol::NVME_TCP)
232 				continue;
233 
234 			if (portal_uses_wildcard_address(portal.get()) &&
235 			    portal->ai()->ai_family != client_sa->sa_family)
236 				continue;
237 
238 			struct nvme_discovery_log_entry entry;
239 			if (init_discovery_log_entry(&entry, port->target(),
240 			    portal.get(), wildcard_host))
241 				discovery_log.append(&entry);
242 		}
243 	}
244 
245 	return discovery_log;
246 }
247 
248 bool
update_cc(uint32_t new_cc)249 discovery_controller::update_cc(uint32_t new_cc)
250 {
251 	uint32_t changes;
252 
253 	if (shutdown)
254 		return false;
255 	if (!nvmf_validate_cc(qp, cap, cc, new_cc))
256 		return false;
257 
258 	changes = cc ^ new_cc;
259 	cc = new_cc;
260 
261 	/* Handle shutdown requests. */
262 	if (NVMEV(NVME_CC_REG_SHN, changes) != 0 &&
263 	    NVMEV(NVME_CC_REG_SHN, new_cc) != 0) {
264 		csts &= ~NVMEM(NVME_CSTS_REG_SHST);
265 		csts |= NVMEF(NVME_CSTS_REG_SHST, NVME_SHST_COMPLETE);
266 		shutdown = true;
267 	}
268 
269 	if (NVMEV(NVME_CC_REG_EN, changes) != 0) {
270 		if (NVMEV(NVME_CC_REG_EN, new_cc) == 0) {
271 			/* Controller reset. */
272 			csts = 0;
273 			shutdown = true;
274 		} else
275 			csts |= NVMEF(NVME_CSTS_REG_RDY, 1);
276 	}
277 	return true;
278 }
279 
280 void
handle_property_get(const struct nvmf_capsule * nc,const struct nvmf_fabric_prop_get_cmd * pget)281 discovery_controller::handle_property_get(const struct nvmf_capsule *nc,
282     const struct nvmf_fabric_prop_get_cmd *pget)
283 {
284 	struct nvmf_fabric_prop_get_rsp rsp;
285 
286 	nvmf_init_cqe(&rsp, nc, 0);
287 
288 	switch (le32toh(pget->ofst)) {
289 	case NVMF_PROP_CAP:
290 		if (pget->attrib.size != NVMF_PROP_SIZE_8)
291 			goto error;
292 		rsp.value.u64 = htole64(cap);
293 		break;
294 	case NVMF_PROP_VS:
295 		if (pget->attrib.size != NVMF_PROP_SIZE_4)
296 			goto error;
297 		rsp.value.u32.low = htole32(vs);
298 		break;
299 	case NVMF_PROP_CC:
300 		if (pget->attrib.size != NVMF_PROP_SIZE_4)
301 			goto error;
302 		rsp.value.u32.low = htole32(cc);
303 		break;
304 	case NVMF_PROP_CSTS:
305 		if (pget->attrib.size != NVMF_PROP_SIZE_4)
306 			goto error;
307 		rsp.value.u32.low = htole32(csts);
308 		break;
309 	default:
310 		goto error;
311 	}
312 
313 	nvmf_send_response(nc, &rsp);
314 	return;
315 error:
316 	nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD);
317 }
318 
319 void
handle_property_set(const struct nvmf_capsule * nc,const struct nvmf_fabric_prop_set_cmd * pset)320 discovery_controller::handle_property_set(const struct nvmf_capsule *nc,
321     const struct nvmf_fabric_prop_set_cmd *pset)
322 {
323 	switch (le32toh(pset->ofst)) {
324 	case NVMF_PROP_CC:
325 		if (pset->attrib.size != NVMF_PROP_SIZE_4)
326 			goto error;
327 		if (!update_cc(le32toh(pset->value.u32.low)))
328 			goto error;
329 		break;
330 	default:
331 		goto error;
332 	}
333 
334 	nvmf_send_success(nc);
335 	return;
336 error:
337 	nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD);
338 }
339 
340 void
handle_fabrics_command(const struct nvmf_capsule * nc,const struct nvmf_fabric_cmd * fc)341 discovery_controller::handle_fabrics_command(const struct nvmf_capsule *nc,
342     const struct nvmf_fabric_cmd *fc)
343 {
344 	switch (fc->fctype) {
345 	case NVMF_FABRIC_COMMAND_PROPERTY_GET:
346 		handle_property_get(nc,
347 		    (const struct nvmf_fabric_prop_get_cmd *)fc);
348 		break;
349 	case NVMF_FABRIC_COMMAND_PROPERTY_SET:
350 		handle_property_set(nc,
351 		    (const struct nvmf_fabric_prop_set_cmd *)fc);
352 		break;
353 	case NVMF_FABRIC_COMMAND_CONNECT:
354 		log_warnx("CONNECT command on connected queue");
355 		nvmf_send_generic_error(nc, NVME_SC_COMMAND_SEQUENCE_ERROR);
356 		break;
357 	case NVMF_FABRIC_COMMAND_DISCONNECT:
358 		log_warnx("DISCONNECT command on admin queue");
359 		nvmf_send_error(nc, NVME_SCT_COMMAND_SPECIFIC,
360 		    NVMF_FABRIC_SC_INVALID_QUEUE_TYPE);
361 		break;
362 	default:
363 		log_warnx("Unsupported fabrics command %#x", fc->fctype);
364 		nvmf_send_generic_error(nc, NVME_SC_INVALID_OPCODE);
365 		break;
366 	}
367 }
368 
369 void
handle_identify_command(const struct nvmf_capsule * nc,const struct nvme_command * cmd)370 discovery_controller::handle_identify_command(const struct nvmf_capsule *nc,
371     const struct nvme_command *cmd)
372 {
373 	uint8_t cns;
374 
375 	cns = le32toh(cmd->cdw10) & 0xFF;
376 	switch (cns) {
377 	case 1:
378 		break;
379 	default:
380 		log_warnx("Unsupported CNS %#x for IDENTIFY", cns);
381 		goto error;
382 	}
383 
384 	nvmf_send_controller_data(nc, &cdata, sizeof(cdata));
385 	return;
386 error:
387 	nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD);
388 }
389 
390 void
handle_get_log_page_command(const struct nvmf_capsule * nc,const struct nvme_command * cmd)391 discovery_controller::handle_get_log_page_command(const struct nvmf_capsule *nc,
392     const struct nvme_command *cmd)
393 {
394 	uint64_t offset;
395 	uint32_t length;
396 
397 	switch (nvmf_get_log_page_id(cmd)) {
398 	case NVME_LOG_DISCOVERY:
399 		break;
400 	default:
401 		log_warnx("Unsupported log page %u for discovery controller",
402 		    nvmf_get_log_page_id(cmd));
403 		goto error;
404 	}
405 
406 	offset = nvmf_get_log_page_offset(cmd);
407 	if (offset >= discovery_log.length())
408 		goto error;
409 
410 	length = nvmf_get_log_page_length(cmd);
411 	if (length > discovery_log.length() - offset)
412 		length = discovery_log.length() - offset;
413 
414 	nvmf_send_controller_data(nc, discovery_log.data() + offset, length);
415 	return;
416 error:
417 	nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD);
418 }
419 
420 void
handle_admin_commands()421 discovery_controller::handle_admin_commands()
422 {
423 	for (;;) {
424 		struct nvmf_capsule *nc;
425 		int error = nvmf_controller_receive_capsule(qp, &nc);
426 		if (error != 0) {
427 			if (error != ECONNRESET)
428 				log_warnc(error,
429 				    "Failed to read command capsule");
430 			break;
431 		}
432 		nvmf_capsule_up nc_guard(nc);
433 
434 		const struct nvme_command *cmd =
435 		    (const struct nvme_command *)nvmf_capsule_sqe(nc);
436 
437 		/*
438 		 * Only permit Fabrics commands while a controller is
439 		 * disabled.
440 		 */
441 		if (NVMEV(NVME_CC_REG_EN, cc) == 0 &&
442 		    cmd->opc != NVME_OPC_FABRICS_COMMANDS) {
443 			log_warnx("Unsupported admin opcode %#x while disabled\n",
444 			    cmd->opc);
445 			nvmf_send_generic_error(nc,
446 			    NVME_SC_COMMAND_SEQUENCE_ERROR);
447 			continue;
448 		}
449 
450 		switch (cmd->opc) {
451 		case NVME_OPC_FABRICS_COMMANDS:
452 			handle_fabrics_command(nc,
453 			    (const struct nvmf_fabric_cmd *)cmd);
454 			break;
455 		case NVME_OPC_IDENTIFY:
456 			handle_identify_command(nc, cmd);
457 			break;
458 		case NVME_OPC_GET_LOG_PAGE:
459 			handle_get_log_page_command(nc, cmd);
460 			break;
461 		default:
462 			log_warnx("Unsupported admin opcode %#x", cmd->opc);
463 			nvmf_send_generic_error(nc, NVME_SC_INVALID_OPCODE);
464 			break;
465 		}
466 	}
467 }
468 
discovery_controller(freebsd::fd_up fd,struct nvmf_qpair * qp,const struct discovery_log & discovery_log)469 discovery_controller::discovery_controller(freebsd::fd_up fd,
470     struct nvmf_qpair *qp, const struct discovery_log &discovery_log) :
471 	qp(qp), discovery_log(discovery_log), s(std::move(fd))
472 {
473 	nvmf_init_discovery_controller_data(qp, &cdata);
474 	cap = nvmf_controller_cap(qp);
475 	vs = cdata.ver;
476 }
477 
478 void
handle_connection(freebsd::fd_up fd,const char * host __unused,const struct sockaddr * client_sa)479 nvmf_discovery_portal::handle_connection(freebsd::fd_up fd,
480     const char *host __unused, const struct sockaddr *client_sa)
481 {
482 	struct nvmf_qpair_params qparams;
483 	memset(&qparams, 0, sizeof(qparams));
484 	qparams.tcp.fd = fd;
485 
486 	struct nvmf_capsule *nc = NULL;
487 	struct nvmf_fabric_connect_data data;
488 	nvmf_qpair_up qp(nvmf_accept(association(), &qparams, &nc, &data));
489 	if (!qp) {
490 		log_warnx("Failed to create NVMe discovery qpair: %s",
491 		    nvmf_association_error(association()));
492 		return;
493 	}
494 	nvmf_capsule_up nc_guard(nc);
495 
496 	if (strncmp((char *)data.subnqn, NVMF_DISCOVERY_NQN,
497 	    sizeof(data.subnqn)) != 0) {
498 		log_warnx("Discovery NVMe qpair with invalid SubNQN: %.*s",
499 		    (int)sizeof(data.subnqn), data.subnqn);
500 		nvmf_connect_invalid_parameters(nc, true,
501 		    offsetof(struct nvmf_fabric_connect_data, subnqn));
502 		return;
503 	}
504 
505 	/* Just use a controller ID of 1 for all discovery controllers. */
506 	int error = nvmf_finish_accept(nc, 1);
507 	if (error != 0) {
508 		log_warnc(error, "Failed to send NVMe CONNECT reponse");
509 		return;
510 	}
511 	nc_guard.reset();
512 
513 	discovery_log discovery_log = build_discovery_log_page(portal_group(),
514 	    fd, client_sa, data);
515 
516 	discovery_controller controller(std::move(fd), qp.get(), discovery_log);
517 	controller.handle_admin_commands();
518 }
519