xref: /spdk/module/bdev/nvme/bdev_mdns_client.c (revision f8abbede89d30584d2a4f8427b13896f8591b873)
1 /*  SPDX-License-Identifier: BSD-3-Clause
2  *  Copyright (c) 2022 Dell Inc, or its subsidiaries.
3  *  All rights reserved.
4  */
5 
6 #include "spdk/stdinc.h"
7 #include "spdk/version.h"
8 
9 #include "spdk_internal/event.h"
10 
11 #include "spdk/assert.h"
12 #include "spdk/config.h"
13 #include "spdk/env.h"
14 #include "spdk/init.h"
15 #include "spdk/log.h"
16 #include "spdk/thread.h"
17 #include "spdk/trace.h"
18 #include "spdk/string.h"
19 #include "spdk/scheduler.h"
20 #include "spdk/rpc.h"
21 #include "spdk/util.h"
22 #include "spdk/nvme.h"
23 #include "bdev_nvme.h"
24 
25 #ifdef SPDK_CONFIG_AVAHI
26 #include <avahi-client/client.h>
27 #include <avahi-client/lookup.h>
28 #include <avahi-common/simple-watch.h>
29 #include <avahi-common/malloc.h>
30 #include <avahi-common/error.h>
31 
32 static AvahiSimplePoll *g_avahi_simple_poll = NULL;
33 static AvahiClient *g_avahi_client = NULL;
34 
35 struct mdns_discovery_entry_ctx {
36 	char                                            name[256];
37 	struct spdk_nvme_transport_id                   trid;
38 	struct spdk_nvme_ctrlr_opts                     drv_opts;
39 	TAILQ_ENTRY(mdns_discovery_entry_ctx)           tailq;
40 	struct mdns_discovery_ctx                       *ctx;
41 };
42 
43 struct mdns_discovery_ctx {
44 	char                                    *name;
45 	char                                    *svcname;
46 	char                                    *hostnqn;
47 	AvahiServiceBrowser                     *sb;
48 	struct spdk_poller                      *poller;
49 	struct spdk_nvme_ctrlr_opts             drv_opts;
50 	struct nvme_ctrlr_opts                  bdev_opts;
51 	uint32_t                                seqno;
52 	bool                                    stop;
53 	struct spdk_thread                      *calling_thread;
54 	TAILQ_ENTRY(mdns_discovery_ctx)         tailq;
55 	TAILQ_HEAD(, mdns_discovery_entry_ctx)  mdns_discovery_entry_ctxs;
56 };
57 
58 TAILQ_HEAD(mdns_discovery_ctxs, mdns_discovery_ctx);
59 static struct mdns_discovery_ctxs g_mdns_discovery_ctxs = TAILQ_HEAD_INITIALIZER(
60 			g_mdns_discovery_ctxs);
61 
62 static struct mdns_discovery_entry_ctx *
63 create_mdns_discovery_entry_ctx(struct mdns_discovery_ctx *ctx, struct spdk_nvme_transport_id *trid)
64 {
65 	struct mdns_discovery_entry_ctx *new_ctx;
66 
67 	assert(ctx);
68 	assert(trid);
69 	new_ctx = calloc(1, sizeof(*new_ctx));
70 	if (new_ctx == NULL) {
71 		SPDK_ERRLOG("could not allocate new mdns_entry_ctx\n");
72 		return NULL;
73 	}
74 
75 	new_ctx->ctx = ctx;
76 	memcpy(&new_ctx->trid, trid, sizeof(struct spdk_nvme_transport_id));
77 	snprintf(new_ctx->name, sizeof(new_ctx->name), "%s%u_nvme", ctx->name, ctx->seqno);
78 	memcpy(&new_ctx->drv_opts, &ctx->drv_opts, sizeof(ctx->drv_opts));
79 	snprintf(new_ctx->drv_opts.hostnqn, sizeof(ctx->drv_opts.hostnqn), "%s", ctx->hostnqn);
80 	ctx->seqno = ctx->seqno + 1;
81 	return new_ctx;
82 }
83 
84 static void
85 mdns_bdev_nvme_start_discovery(void *_entry_ctx)
86 {
87 	int status;
88 	struct mdns_discovery_entry_ctx *entry_ctx = _entry_ctx;
89 
90 	assert(_entry_ctx);
91 	status = bdev_nvme_start_discovery(&entry_ctx->trid, entry_ctx->name,
92 					   &entry_ctx->ctx->drv_opts,
93 					   &entry_ctx->ctx->bdev_opts,
94 					   0, true, NULL, NULL);
95 	if (status) {
96 		SPDK_ERRLOG("Error starting discovery for name %s addr %s port %s subnqn %s &trid %p\n",
97 			    entry_ctx->ctx->name, entry_ctx->trid.traddr, entry_ctx->trid.trsvcid,
98 			    entry_ctx->trid.subnqn, &entry_ctx->trid);
99 	}
100 }
101 
102 static void
103 free_mdns_discovery_entry_ctx(struct mdns_discovery_ctx *ctx)
104 {
105 	struct mdns_discovery_entry_ctx *entry_ctx, *tmp;
106 
107 	if (!ctx) {
108 		return;
109 	}
110 
111 	TAILQ_FOREACH_SAFE(entry_ctx, &ctx->mdns_discovery_entry_ctxs, tailq, tmp) {
112 		TAILQ_REMOVE(&ctx->mdns_discovery_entry_ctxs, entry_ctx, tailq);
113 		free(entry_ctx);
114 	}
115 }
116 
117 static void
118 free_mdns_discovery_ctx(struct mdns_discovery_ctx *ctx)
119 {
120 	if (!ctx) {
121 		return;
122 	}
123 
124 	free(ctx->name);
125 	free(ctx->svcname);
126 	free(ctx->hostnqn);
127 	avahi_service_browser_free(ctx->sb);
128 	free_mdns_discovery_entry_ctx(ctx);
129 	free(ctx);
130 }
131 
132 /* get_key_val_avahi_resolve_txt - Search for the key string in the TXT received
133  *                            from Avavi daemon and return its value.
134  *   input
135  *       txt: TXT returned by Ahavi daemon will be of format
136  *            "NQN=nqn.1988-11.com.dell:SFSS:1:20221122170722e8" "p=tcp foo" and the
137  *            AvahiStringList txt is a linked list with each node holding a
138  *            key-value pair like key:p value:tcp
139  *
140  *       key: Key string to search in the txt list
141  *   output
142  *       Returns the value for the key or NULL if key is not present
143  *       Returned string needs to be freed with avahi_free()
144  */
145 static char *
146 get_key_val_avahi_resolve_txt(AvahiStringList *txt, const char *key)
147 {
148 	char *k = NULL, *v = NULL;
149 	AvahiStringList *p = NULL;
150 	int r;
151 
152 	if (!txt || !key) {
153 		return NULL;
154 	}
155 
156 	p = avahi_string_list_find(txt, key);
157 	if (!p) {
158 		return NULL;
159 	}
160 
161 	r = avahi_string_list_get_pair(p, &k, &v, NULL);
162 	if (r < 0) {
163 		return NULL;
164 	}
165 
166 	avahi_free(k);
167 	return v;
168 }
169 
170 static int
171 get_spdk_nvme_transport_from_proto_str(char *protocol, enum spdk_nvme_transport_type *trtype)
172 {
173 	int status = -1;
174 
175 	if (!protocol || !trtype) {
176 		return status;
177 	}
178 
179 	if (strcmp("tcp", protocol) == 0) {
180 		*trtype = SPDK_NVME_TRANSPORT_TCP;
181 		return 0;
182 	}
183 
184 	return status;
185 }
186 
187 static enum spdk_nvmf_adrfam
188 get_spdk_nvme_adrfam_from_avahi_addr(const AvahiAddress *address) {
189 
190 	if (!address)
191 	{
192 		/* Return ipv4 by default */
193 		return SPDK_NVMF_ADRFAM_IPV4;
194 	}
195 
196 	switch (address->proto)
197 	{
198 	case AVAHI_PROTO_INET:
199 		return SPDK_NVMF_ADRFAM_IPV4;
200 	case AVAHI_PROTO_INET6:
201 		return SPDK_NVMF_ADRFAM_IPV6;
202 	default:
203 		return SPDK_NVMF_ADRFAM_IPV4;
204 	}
205 }
206 
207 static struct mdns_discovery_ctx *
208 get_mdns_discovery_ctx_by_svcname(const char *svcname)
209 {
210 	struct mdns_discovery_ctx *ctx = NULL, *tmp_ctx = NULL;
211 
212 	if (!svcname) {
213 		return NULL;
214 	}
215 
216 	TAILQ_FOREACH_SAFE(ctx, &g_mdns_discovery_ctxs, tailq, tmp_ctx) {
217 		if (strcmp(ctx->svcname, svcname) == 0) {
218 			return ctx;
219 		}
220 	}
221 	return NULL;
222 }
223 
224 static void
225 mdns_resolve_callback(
226 	AvahiServiceResolver *r,
227 	AVAHI_GCC_UNUSED AvahiIfIndex interface,
228 	AVAHI_GCC_UNUSED AvahiProtocol protocol,
229 	AvahiResolverEvent event,
230 	const char *name,
231 	const char *type,
232 	const char *domain,
233 	const char *host_name,
234 	const AvahiAddress *address,
235 	uint16_t port,
236 	AvahiStringList *txt,
237 	AvahiLookupResultFlags flags,
238 	AVAHI_GCC_UNUSED void *userdata)
239 {
240 	assert(r);
241 	/* Called whenever a service has been resolved successfully or timed out */
242 	switch (event) {
243 	case AVAHI_RESOLVER_FAILURE:
244 		SPDK_ERRLOG("(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n",
245 			    name, type, domain,
246 			    avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
247 		break;
248 	case AVAHI_RESOLVER_FOUND: {
249 		char ipaddr[SPDK_NVMF_TRADDR_MAX_LEN + 1], port_str[SPDK_NVMF_TRSVCID_MAX_LEN + 1], *t;
250 		struct spdk_nvme_transport_id *trid = NULL;
251 		char *subnqn = NULL, *proto = NULL;
252 		struct mdns_discovery_ctx *ctx = NULL;
253 		struct mdns_discovery_entry_ctx *entry_ctx = NULL;
254 		int status = -1;
255 
256 		memset(ipaddr, 0, sizeof(ipaddr));
257 		memset(port_str, 0, sizeof(port_str));
258 		SPDK_INFOLOG(bdev_nvme, "Service '%s' of type '%s' in domain '%s'\n", name, type, domain);
259 		avahi_address_snprint(ipaddr, sizeof(ipaddr), address);
260 		snprintf(port_str, sizeof(port_str), "%d", port);
261 		t = avahi_string_list_to_string(txt);
262 		SPDK_INFOLOG(bdev_nvme,
263 			     "\t%s:%u (%s)\n"
264 			     "\tTXT=%s\n"
265 			     "\tcookie is %u\n"
266 			     "\tis_local: %i\n"
267 			     "\tour_own: %i\n"
268 			     "\twide_area: %i\n"
269 			     "\tmulticast: %i\n"
270 			     "\tcached: %i\n",
271 			     host_name, port, ipaddr,
272 			     t,
273 			     avahi_string_list_get_service_cookie(txt),
274 			     !!(flags & AVAHI_LOOKUP_RESULT_LOCAL),
275 			     !!(flags & AVAHI_LOOKUP_RESULT_OUR_OWN),
276 			     !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA),
277 			     !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST),
278 			     !!(flags & AVAHI_LOOKUP_RESULT_CACHED));
279 		avahi_free(t);
280 
281 		ctx = get_mdns_discovery_ctx_by_svcname(type);
282 		if (!ctx) {
283 			SPDK_ERRLOG("Unknown Service '%s'\n", type);
284 			break;
285 		}
286 
287 		trid = (struct spdk_nvme_transport_id *) calloc(1, sizeof(struct spdk_nvme_transport_id));
288 		if (!trid) {
289 			SPDK_ERRLOG(" Error allocating memory for trid\n");
290 			break;
291 		}
292 		trid->adrfam = get_spdk_nvme_adrfam_from_avahi_addr(address);
293 		if (trid->adrfam != SPDK_NVMF_ADRFAM_IPV4) {
294 			/* TODO: For now process only ipv4 addresses */
295 			SPDK_INFOLOG(bdev_nvme, "trid family is not IPV4 %d\n", trid->adrfam);
296 			free(trid);
297 			break;
298 		}
299 		subnqn = get_key_val_avahi_resolve_txt(txt, "NQN");
300 		if (!subnqn) {
301 			free(trid);
302 			SPDK_ERRLOG("subnqn received is empty for service %s\n", ctx->svcname);
303 			break;
304 		}
305 		proto = get_key_val_avahi_resolve_txt(txt, "p");
306 		if (!proto) {
307 			free(trid);
308 			avahi_free(subnqn);
309 			SPDK_ERRLOG("Protocol not received for service %s\n", ctx->svcname);
310 			break;
311 		}
312 		status = get_spdk_nvme_transport_from_proto_str(proto, &trid->trtype);
313 		if (status) {
314 			free(trid);
315 			avahi_free(subnqn);
316 			avahi_free(proto);
317 			SPDK_ERRLOG("Unable to derive nvme transport type  for service %s\n", ctx->svcname);
318 			break;
319 		}
320 		snprintf(trid->traddr, sizeof(trid->traddr), "%s", ipaddr);
321 		snprintf(trid->trsvcid, sizeof(trid->trsvcid), "%s", port_str);
322 		snprintf(trid->subnqn, sizeof(trid->subnqn), "%s", subnqn);
323 		TAILQ_FOREACH(entry_ctx, &ctx->mdns_discovery_entry_ctxs, tailq) {
324 			if (!spdk_nvme_transport_id_compare(trid, &entry_ctx->trid)) {
325 				SPDK_ERRLOG("mDNS discovery entry exists already. trid->traddr: %s trid->trsvcid: %s\n",
326 					    trid->traddr, trid->trsvcid);
327 				free(trid);
328 				avahi_free(subnqn);
329 				avahi_free(proto);
330 				avahi_service_resolver_free(r);
331 				return;
332 			}
333 		}
334 		entry_ctx = create_mdns_discovery_entry_ctx(ctx, trid);
335 		TAILQ_INSERT_TAIL(&ctx->mdns_discovery_entry_ctxs, entry_ctx, tailq);
336 		spdk_thread_send_msg(ctx->calling_thread, mdns_bdev_nvme_start_discovery, entry_ctx);
337 		free(trid);
338 		avahi_free(subnqn);
339 		avahi_free(proto);
340 		break;
341 	}
342 	default:
343 		SPDK_ERRLOG("Unknown Avahi resolver event: %d", event);
344 	}
345 	avahi_service_resolver_free(r);
346 }
347 
348 static void
349 mdns_browse_callback(
350 	AvahiServiceBrowser *b,
351 	AvahiIfIndex interface,
352 	AvahiProtocol protocol,
353 	AvahiBrowserEvent event,
354 	const char *name,
355 	const char *type,
356 	const char *domain,
357 	AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
358 	void *userdata)
359 {
360 	AvahiClient *c = userdata;
361 
362 	assert(b);
363 	/* Called whenever a new services becomes available on the LAN or is removed from the LAN */
364 	switch (event) {
365 	case AVAHI_BROWSER_FAILURE:
366 		SPDK_ERRLOG("(Browser) Failure: %s\n",
367 			    avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))));
368 		return;
369 	case AVAHI_BROWSER_NEW:
370 		SPDK_DEBUGLOG(bdev_nvme, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type,
371 			      domain);
372 		/* We ignore the returned resolver object. In the callback
373 		   function we free it. If the server is terminated before
374 		   the callback function is called the server will free
375 		   the resolver for us. */
376 		if (!(avahi_service_resolver_new(c, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0,
377 						 mdns_resolve_callback, c))) {
378 			SPDK_ERRLOG("Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_client_errno(c)));
379 		}
380 		break;
381 	case AVAHI_BROWSER_REMOVE:
382 		SPDK_ERRLOG("(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
383 		/* On remove, we are not doing the automatic cleanup of connections
384 		 * to the targets that were learnt from the CDC, for which remove event has
385 		 * been received. If required, user can clear the connections manually by
386 		 * invoking bdev_nvme_stop_discovery. We can implement the automatic cleanup
387 		 * later, if there is a requirement in the future.
388 		 */
389 		break;
390 	case AVAHI_BROWSER_ALL_FOR_NOW:
391 	case AVAHI_BROWSER_CACHE_EXHAUSTED:
392 		SPDK_INFOLOG(bdev_nvme, "(Browser) %s\n",
393 			     event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
394 		break;
395 	default:
396 		SPDK_ERRLOG("Unknown Avahi browser event: %d", event);
397 	}
398 }
399 
400 static void
401 client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void *userdata)
402 {
403 	assert(c);
404 	/* Called whenever the client or server state changes */
405 	if (state == AVAHI_CLIENT_FAILURE) {
406 		SPDK_ERRLOG("Server connection failure: %s\n", avahi_strerror(avahi_client_errno(c)));
407 	}
408 }
409 
410 static int
411 bdev_nvme_avahi_iterate(void *arg)
412 {
413 	struct mdns_discovery_ctx *ctx = arg;
414 	int rc;
415 
416 	if (ctx->stop) {
417 		SPDK_INFOLOG(bdev_nvme, "Stopping avahi poller for service %s\n", ctx->svcname);
418 		spdk_poller_unregister(&ctx->poller);
419 		TAILQ_REMOVE(&g_mdns_discovery_ctxs, ctx, tailq);
420 		free_mdns_discovery_ctx(ctx);
421 		return SPDK_POLLER_IDLE;
422 	}
423 
424 	if (g_avahi_simple_poll == NULL) {
425 		spdk_poller_unregister(&ctx->poller);
426 		return SPDK_POLLER_IDLE;
427 	}
428 
429 	rc = avahi_simple_poll_iterate(g_avahi_simple_poll, 0);
430 	if (rc && rc != -EAGAIN) {
431 		SPDK_ERRLOG("avahi poll returned error for service: %s/n", ctx->svcname);
432 		return SPDK_POLLER_IDLE;
433 	}
434 
435 	return SPDK_POLLER_BUSY;
436 }
437 
438 static void
439 start_mdns_discovery_poller(void *arg)
440 {
441 	struct mdns_discovery_ctx *ctx = arg;
442 
443 	assert(arg);
444 	TAILQ_INSERT_TAIL(&g_mdns_discovery_ctxs, ctx, tailq);
445 	ctx->poller = SPDK_POLLER_REGISTER(bdev_nvme_avahi_iterate, ctx, 100 * 1000);
446 }
447 
448 int
449 bdev_nvme_start_mdns_discovery(const char *base_name,
450 			       const char *svcname,
451 			       struct spdk_nvme_ctrlr_opts *drv_opts,
452 			       struct nvme_ctrlr_opts *bdev_opts)
453 {
454 	AvahiServiceBrowser *sb = NULL;
455 	int error;
456 	struct mdns_discovery_ctx *ctx;
457 
458 	assert(base_name);
459 	assert(svcname);
460 
461 	TAILQ_FOREACH(ctx, &g_mdns_discovery_ctxs, tailq) {
462 		if (strcmp(ctx->name, base_name) == 0) {
463 			SPDK_ERRLOG("mDNS discovery already running with name %s\n", base_name);
464 			return -EEXIST;
465 		}
466 
467 		if (strcmp(ctx->svcname, svcname) == 0) {
468 			SPDK_ERRLOG("mDNS discovery already running for service %s\n", svcname);
469 			return -EEXIST;
470 		}
471 	}
472 
473 	if (g_avahi_simple_poll == NULL) {
474 
475 		/* Allocate main loop object */
476 		if (!(g_avahi_simple_poll = avahi_simple_poll_new())) {
477 			SPDK_ERRLOG("Failed to create poll object for mDNS discovery for service: %s.\n", svcname);
478 			return -ENOMEM;
479 		}
480 	}
481 
482 	if (g_avahi_client == NULL) {
483 
484 		/* Allocate a new client */
485 		g_avahi_client = avahi_client_new(avahi_simple_poll_get(g_avahi_simple_poll), 0, client_callback,
486 						  NULL, &error);
487 		/* Check whether creating the client object succeeded */
488 		if (!g_avahi_client) {
489 			SPDK_ERRLOG("Failed to create mDNS client for service:%s Error: %s\n", svcname,
490 				    avahi_strerror(error));
491 			return -ENOMEM;
492 		}
493 	}
494 
495 	/* Create the service browser */
496 	if (!(sb = avahi_service_browser_new(g_avahi_client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, svcname,
497 					     NULL, 0, mdns_browse_callback, g_avahi_client))) {
498 		SPDK_ERRLOG("Failed to create service browser for service: %s Error: %s\n", svcname,
499 			    avahi_strerror(avahi_client_errno(g_avahi_client)));
500 		return -ENOMEM;
501 	}
502 
503 	ctx = calloc(1, sizeof(*ctx));
504 	if (ctx == NULL) {
505 		SPDK_ERRLOG("Error creating mDNS discovery ctx for service: %s\n", svcname);
506 		avahi_service_browser_free(sb);
507 		return -ENOMEM;
508 	}
509 
510 	ctx->svcname = strdup(svcname);
511 	if (ctx->svcname == NULL) {
512 		SPDK_ERRLOG("Error creating mDNS discovery ctx svcname for service: %s\n", svcname);
513 		free_mdns_discovery_ctx(ctx);
514 		avahi_service_browser_free(sb);
515 		return -ENOMEM;
516 	}
517 	ctx->name = strdup(base_name);
518 	if (ctx->name == NULL) {
519 		SPDK_ERRLOG("Error creating mDNS discovery ctx name for service: %s\n", svcname);
520 		free_mdns_discovery_ctx(ctx);
521 		avahi_service_browser_free(sb);
522 		return -ENOMEM;
523 	}
524 	memcpy(&ctx->drv_opts, drv_opts, sizeof(*drv_opts));
525 	memcpy(&ctx->bdev_opts, bdev_opts, sizeof(*bdev_opts));
526 	ctx->sb = sb;
527 	ctx->calling_thread = spdk_get_thread();
528 	TAILQ_INIT(&ctx->mdns_discovery_entry_ctxs);
529 	/* Even if user did not specify hostnqn, we can still strdup("\0"); */
530 	ctx->hostnqn = strdup(ctx->drv_opts.hostnqn);
531 	if (ctx->hostnqn == NULL) {
532 		SPDK_ERRLOG("Error creating mDNS discovery ctx hostnqn for service: %s\n", svcname);
533 		free_mdns_discovery_ctx(ctx);
534 		return -ENOMEM;
535 	}
536 	/* Start the poller for the Avahi client browser in g_bdev_nvme_init_thread */
537 	spdk_thread_send_msg(g_bdev_nvme_init_thread, start_mdns_discovery_poller, ctx);
538 	return 0;
539 }
540 
541 static void
542 mdns_stop_discovery_entry(struct mdns_discovery_ctx *ctx)
543 {
544 	struct mdns_discovery_entry_ctx *entry_ctx = NULL;
545 
546 	assert(ctx);
547 
548 	TAILQ_FOREACH(entry_ctx, &ctx->mdns_discovery_entry_ctxs, tailq) {
549 		bdev_nvme_stop_discovery(entry_ctx->name, NULL, NULL);
550 	}
551 }
552 
553 int
554 bdev_nvme_stop_mdns_discovery(const char *name)
555 {
556 	struct mdns_discovery_ctx *ctx;
557 
558 	assert(name);
559 	TAILQ_FOREACH(ctx, &g_mdns_discovery_ctxs, tailq) {
560 		if (strcmp(name, ctx->name) == 0) {
561 			if (ctx->stop) {
562 				return -EALREADY;
563 			}
564 			/* set stop to true to stop the mdns poller instance */
565 			ctx->stop = true;
566 			mdns_stop_discovery_entry(ctx);
567 			return 0;
568 		}
569 	}
570 
571 	return -ENOENT;
572 }
573 
574 void
575 bdev_nvme_get_mdns_discovery_info(struct spdk_jsonrpc_request *request)
576 {
577 	struct mdns_discovery_ctx *ctx;
578 	struct mdns_discovery_entry_ctx *entry_ctx;
579 	struct spdk_json_write_ctx *w;
580 
581 	w = spdk_jsonrpc_begin_result(request);
582 	spdk_json_write_array_begin(w);
583 	TAILQ_FOREACH(ctx, &g_mdns_discovery_ctxs, tailq) {
584 		spdk_json_write_object_begin(w);
585 		spdk_json_write_named_string(w, "name", ctx->name);
586 		spdk_json_write_named_string(w, "svcname", ctx->svcname);
587 
588 		spdk_json_write_named_array_begin(w, "referrals");
589 		TAILQ_FOREACH(entry_ctx, &ctx->mdns_discovery_entry_ctxs, tailq) {
590 			spdk_json_write_object_begin(w);
591 			spdk_json_write_named_string(w, "name", entry_ctx->name);
592 			spdk_json_write_named_object_begin(w, "trid");
593 			nvme_bdev_dump_trid_json(&entry_ctx->trid, w);
594 			spdk_json_write_object_end(w);
595 			spdk_json_write_object_end(w);
596 		}
597 		spdk_json_write_array_end(w);
598 
599 		spdk_json_write_object_end(w);
600 	}
601 	spdk_json_write_array_end(w);
602 	spdk_jsonrpc_end_result(request, w);
603 }
604 
605 void
606 bdev_nvme_mdns_discovery_config_json(struct spdk_json_write_ctx *w)
607 {
608 	struct mdns_discovery_ctx *ctx;
609 
610 	TAILQ_FOREACH(ctx, &g_mdns_discovery_ctxs, tailq) {
611 		spdk_json_write_object_begin(w);
612 
613 		spdk_json_write_named_string(w, "method", "bdev_nvme_start_mdns_discovery");
614 
615 		spdk_json_write_named_object_begin(w, "params");
616 		spdk_json_write_named_string(w, "name", ctx->name);
617 		spdk_json_write_named_string(w, "svcname", ctx->svcname);
618 		spdk_json_write_named_string(w, "hostnqn", ctx->hostnqn);
619 		spdk_json_write_object_end(w);
620 
621 		spdk_json_write_object_end(w);
622 	}
623 }
624 
625 #else /* SPDK_CONFIG_AVAHI */
626 
627 int
628 bdev_nvme_start_mdns_discovery(const char *base_name,
629 			       const char *svcname,
630 			       struct spdk_nvme_ctrlr_opts *drv_opts,
631 			       struct nvme_ctrlr_opts *bdev_opts)
632 {
633 	SPDK_ERRLOG("spdk not built with --with-avahi option\n");
634 	return -ENOTSUP;
635 }
636 
637 int
638 bdev_nvme_stop_mdns_discovery(const char *name)
639 {
640 	SPDK_ERRLOG("spdk not built with --with-avahi option\n");
641 	return -ENOTSUP;
642 }
643 
644 void
645 bdev_nvme_get_mdns_discovery_info(struct spdk_jsonrpc_request *request)
646 {
647 	SPDK_ERRLOG("spdk not built with --with-avahi option\n");
648 	spdk_jsonrpc_send_error_response(request, -ENOTSUP, spdk_strerror(ENOTSUP));
649 }
650 
651 void
652 bdev_nvme_mdns_discovery_config_json(struct spdk_json_write_ctx *w)
653 {
654 	/* Empty function to be invoked, when SPDK is built without --with-avahi */
655 }
656 
657 #endif
658