xref: /spdk/module/bdev/nvme/bdev_mdns_client.c (revision 42fd001310188f0635a3953f3b0ea0b33a840902)
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_handler(
226 	AvahiServiceResolver *resolver,
227 	AVAHI_GCC_UNUSED AvahiIfIndex intf,
228 	AVAHI_GCC_UNUSED AvahiProtocol avahi_protocol,
229 	AvahiResolverEvent resolve_event,
230 	const char *svc_name,
231 	const char *svc_type,
232 	const char *svc_domain,
233 	const char *host_name,
234 	const AvahiAddress *host_address,
235 	uint16_t port,
236 	AvahiStringList *txt,
237 	AvahiLookupResultFlags result_flags,
238 	AVAHI_GCC_UNUSED void *user_data)
239 {
240 	assert(resolver);
241 	/* The handler gets called whenever a service has been resolved
242 	   successfully or timed out */
243 	switch (resolve_event) {
244 	case AVAHI_RESOLVER_FOUND: {
245 		char ipaddr[SPDK_NVMF_TRADDR_MAX_LEN + 1], port_str[SPDK_NVMF_TRSVCID_MAX_LEN + 1], *str;
246 		struct spdk_nvme_transport_id *trid = NULL;
247 		char *subnqn = NULL, *proto = NULL;
248 		struct mdns_discovery_ctx *ctx = NULL;
249 		struct mdns_discovery_entry_ctx *entry_ctx = NULL;
250 		int status = -1;
251 
252 		memset(ipaddr, 0, sizeof(ipaddr));
253 		memset(port_str, 0, sizeof(port_str));
254 		SPDK_INFOLOG(bdev_nvme, "Service '%s' of type '%s' in domain '%s'\n", svc_name, svc_type,
255 			     svc_domain);
256 		avahi_address_snprint(ipaddr, sizeof(ipaddr), host_address);
257 		snprintf(port_str, sizeof(port_str), "%d", port);
258 		str = avahi_string_list_to_string(txt);
259 		SPDK_INFOLOG(bdev_nvme,
260 			     "\t%s:%u (%s)\n"
261 			     "\tTXT=%s\n"
262 			     "\tcookie is %u\n"
263 			     "\tis_local: %i\n"
264 			     "\tour_own: %i\n"
265 			     "\twide_area: %i\n"
266 			     "\tmulticast: %i\n"
267 			     "\tcached: %i\n",
268 			     host_name, port, ipaddr,
269 			     str,
270 			     avahi_string_list_get_service_cookie(txt),
271 			     !!(result_flags & AVAHI_LOOKUP_RESULT_LOCAL),
272 			     !!(result_flags & AVAHI_LOOKUP_RESULT_OUR_OWN),
273 			     !!(result_flags & AVAHI_LOOKUP_RESULT_WIDE_AREA),
274 			     !!(result_flags & AVAHI_LOOKUP_RESULT_MULTICAST),
275 			     !!(result_flags & AVAHI_LOOKUP_RESULT_CACHED));
276 		avahi_free(str);
277 
278 		ctx = get_mdns_discovery_ctx_by_svcname(svc_type);
279 		if (!ctx) {
280 			SPDK_ERRLOG("Unknown Service '%s'\n", svc_type);
281 			break;
282 		}
283 
284 		trid = (struct spdk_nvme_transport_id *) calloc(1, sizeof(struct spdk_nvme_transport_id));
285 		if (!trid) {
286 			SPDK_ERRLOG(" Error allocating memory for trid\n");
287 			break;
288 		}
289 		trid->adrfam = get_spdk_nvme_adrfam_from_avahi_addr(host_address);
290 		if (trid->adrfam != SPDK_NVMF_ADRFAM_IPV4) {
291 			/* TODO: For now process only ipv4 addresses */
292 			SPDK_INFOLOG(bdev_nvme, "trid family is not IPV4 %d\n", trid->adrfam);
293 			free(trid);
294 			break;
295 		}
296 		subnqn = get_key_val_avahi_resolve_txt(txt, "NQN");
297 		if (!subnqn) {
298 			free(trid);
299 			SPDK_ERRLOG("subnqn received is empty for service %s\n", ctx->svcname);
300 			break;
301 		}
302 		proto = get_key_val_avahi_resolve_txt(txt, "p");
303 		if (!proto) {
304 			free(trid);
305 			avahi_free(subnqn);
306 			SPDK_ERRLOG("Protocol not received for service %s\n", ctx->svcname);
307 			break;
308 		}
309 		status = get_spdk_nvme_transport_from_proto_str(proto, &trid->trtype);
310 		if (status) {
311 			free(trid);
312 			avahi_free(subnqn);
313 			avahi_free(proto);
314 			SPDK_ERRLOG("Unable to derive nvme transport type  for service %s\n", ctx->svcname);
315 			break;
316 		}
317 		snprintf(trid->traddr, sizeof(trid->traddr), "%s", ipaddr);
318 		snprintf(trid->trsvcid, sizeof(trid->trsvcid), "%s", port_str);
319 		snprintf(trid->subnqn, sizeof(trid->subnqn), "%s", subnqn);
320 		TAILQ_FOREACH(entry_ctx, &ctx->mdns_discovery_entry_ctxs, tailq) {
321 			if (!spdk_nvme_transport_id_compare(trid, &entry_ctx->trid)) {
322 				SPDK_ERRLOG("mDNS discovery entry exists already. trid->traddr: %s trid->trsvcid: %s\n",
323 					    trid->traddr, trid->trsvcid);
324 				free(trid);
325 				avahi_free(subnqn);
326 				avahi_free(proto);
327 				avahi_service_resolver_free(resolver);
328 				return;
329 			}
330 		}
331 		entry_ctx = create_mdns_discovery_entry_ctx(ctx, trid);
332 		TAILQ_INSERT_TAIL(&ctx->mdns_discovery_entry_ctxs, entry_ctx, tailq);
333 		spdk_thread_send_msg(ctx->calling_thread, mdns_bdev_nvme_start_discovery, entry_ctx);
334 		free(trid);
335 		avahi_free(subnqn);
336 		avahi_free(proto);
337 		break;
338 	}
339 	case AVAHI_RESOLVER_FAILURE:
340 		SPDK_ERRLOG("(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n",
341 			    svc_name, svc_type, svc_domain,
342 			    avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(resolver))));
343 		break;
344 	default:
345 		SPDK_ERRLOG("Unknown Avahi resolver event: %d", resolve_event);
346 	}
347 	avahi_service_resolver_free(resolver);
348 }
349 
350 static void
351 mdns_browse_handler(
352 	AvahiServiceBrowser *browser,
353 	AvahiIfIndex intf,
354 	AvahiProtocol avahi_protocol,
355 	AvahiBrowserEvent browser_event,
356 	const char *svc_name,
357 	const char *svc_type,
358 	const char *svc_domain,
359 	AVAHI_GCC_UNUSED AvahiLookupResultFlags result_flags,
360 	void *user_data)
361 {
362 	AvahiClient *client = user_data;
363 
364 	assert(browser);
365 	/* The handler gets called whenever a new service becomes available
366 	   or removed from the LAN */
367 	switch (browser_event) {
368 	case AVAHI_BROWSER_NEW:
369 		SPDK_DEBUGLOG(bdev_nvme, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", svc_name,
370 			      svc_type,
371 			      svc_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(client, intf, avahi_protocol, svc_name, svc_type, svc_domain,
377 						 AVAHI_PROTO_UNSPEC, 0,
378 						 mdns_resolve_handler, client))) {
379 			SPDK_ERRLOG("Failed to resolve service '%s': %s\n", svc_name,
380 				    avahi_strerror(avahi_client_errno(client)));
381 		}
382 		break;
383 	case AVAHI_BROWSER_REMOVE:
384 		SPDK_ERRLOG("(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", svc_name, svc_type,
385 			    svc_domain);
386 		/* On remove, we are not doing the automatic cleanup of connections
387 		 * to the targets that were learnt from the CDC, for which remove event has
388 		 * been received. If required, user can clear the connections manually by
389 		 * invoking bdev_nvme_stop_discovery. We can implement the automatic cleanup
390 		 * later, if there is a requirement in the future.
391 		 */
392 		break;
393 	case AVAHI_BROWSER_ALL_FOR_NOW:
394 	case AVAHI_BROWSER_CACHE_EXHAUSTED:
395 		SPDK_INFOLOG(bdev_nvme, "(Browser) %s\n",
396 			     browser_event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
397 		break;
398 	case AVAHI_BROWSER_FAILURE:
399 		SPDK_ERRLOG("(Browser) Failure: %s\n",
400 			    avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(browser))));
401 		return;
402 	default:
403 		SPDK_ERRLOG("Unknown Avahi browser event: %d", browser_event);
404 	}
405 }
406 
407 static void
408 client_handler(AvahiClient *client, AvahiClientState avahi_state, AVAHI_GCC_UNUSED void *user_data)
409 {
410 	assert(client);
411 	/* The handler gets called whenever the client or server state changes */
412 	if (avahi_state == AVAHI_CLIENT_FAILURE) {
413 		SPDK_ERRLOG("Server connection failure: %s\n", avahi_strerror(avahi_client_errno(client)));
414 	}
415 }
416 
417 static int
418 bdev_nvme_avahi_iterate(void *arg)
419 {
420 	struct mdns_discovery_ctx *ctx = arg;
421 	int rc;
422 
423 	if (ctx->stop) {
424 		SPDK_INFOLOG(bdev_nvme, "Stopping avahi poller for service %s\n", ctx->svcname);
425 		spdk_poller_unregister(&ctx->poller);
426 		TAILQ_REMOVE(&g_mdns_discovery_ctxs, ctx, tailq);
427 		free_mdns_discovery_ctx(ctx);
428 		return SPDK_POLLER_IDLE;
429 	}
430 
431 	if (g_avahi_simple_poll == NULL) {
432 		spdk_poller_unregister(&ctx->poller);
433 		return SPDK_POLLER_IDLE;
434 	}
435 
436 	rc = avahi_simple_poll_iterate(g_avahi_simple_poll, 0);
437 	if (rc && rc != -EAGAIN) {
438 		SPDK_ERRLOG("avahi poll returned error for service: %s/n", ctx->svcname);
439 		return SPDK_POLLER_IDLE;
440 	}
441 
442 	return SPDK_POLLER_BUSY;
443 }
444 
445 static void
446 start_mdns_discovery_poller(void *arg)
447 {
448 	struct mdns_discovery_ctx *ctx = arg;
449 
450 	assert(arg);
451 	TAILQ_INSERT_TAIL(&g_mdns_discovery_ctxs, ctx, tailq);
452 	ctx->poller = SPDK_POLLER_REGISTER(bdev_nvme_avahi_iterate, ctx, 100 * 1000);
453 }
454 
455 int
456 bdev_nvme_start_mdns_discovery(const char *base_name,
457 			       const char *svcname,
458 			       struct spdk_nvme_ctrlr_opts *drv_opts,
459 			       struct nvme_ctrlr_opts *bdev_opts)
460 {
461 	AvahiServiceBrowser *sb = NULL;
462 	int error;
463 	struct mdns_discovery_ctx *ctx;
464 
465 	assert(base_name);
466 	assert(svcname);
467 
468 	TAILQ_FOREACH(ctx, &g_mdns_discovery_ctxs, tailq) {
469 		if (strcmp(ctx->name, base_name) == 0) {
470 			SPDK_ERRLOG("mDNS discovery already running with name %s\n", base_name);
471 			return -EEXIST;
472 		}
473 
474 		if (strcmp(ctx->svcname, svcname) == 0) {
475 			SPDK_ERRLOG("mDNS discovery already running for service %s\n", svcname);
476 			return -EEXIST;
477 		}
478 	}
479 
480 	if (g_avahi_simple_poll == NULL) {
481 
482 		/* Allocate main loop object */
483 		if (!(g_avahi_simple_poll = avahi_simple_poll_new())) {
484 			SPDK_ERRLOG("Failed to create poll object for mDNS discovery for service: %s.\n", svcname);
485 			return -ENOMEM;
486 		}
487 	}
488 
489 	if (g_avahi_client == NULL) {
490 
491 		/* Allocate a new client */
492 		g_avahi_client = avahi_client_new(avahi_simple_poll_get(g_avahi_simple_poll), 0, client_handler,
493 						  NULL, &error);
494 		/* Check whether creating the client object succeeded */
495 		if (!g_avahi_client) {
496 			SPDK_ERRLOG("Failed to create mDNS client for service:%s Error: %s\n", svcname,
497 				    avahi_strerror(error));
498 			return -ENOMEM;
499 		}
500 	}
501 
502 	/* Create the service browser */
503 	if (!(sb = avahi_service_browser_new(g_avahi_client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, svcname,
504 					     NULL, 0, mdns_browse_handler, g_avahi_client))) {
505 		SPDK_ERRLOG("Failed to create service browser for service: %s Error: %s\n", svcname,
506 			    avahi_strerror(avahi_client_errno(g_avahi_client)));
507 		return -ENOMEM;
508 	}
509 
510 	ctx = calloc(1, sizeof(*ctx));
511 	if (ctx == NULL) {
512 		SPDK_ERRLOG("Error creating mDNS discovery ctx for service: %s\n", svcname);
513 		avahi_service_browser_free(sb);
514 		return -ENOMEM;
515 	}
516 
517 	ctx->svcname = strdup(svcname);
518 	if (ctx->svcname == NULL) {
519 		SPDK_ERRLOG("Error creating mDNS discovery ctx svcname for service: %s\n", svcname);
520 		free_mdns_discovery_ctx(ctx);
521 		avahi_service_browser_free(sb);
522 		return -ENOMEM;
523 	}
524 	ctx->name = strdup(base_name);
525 	if (ctx->name == NULL) {
526 		SPDK_ERRLOG("Error creating mDNS discovery ctx name for service: %s\n", svcname);
527 		free_mdns_discovery_ctx(ctx);
528 		avahi_service_browser_free(sb);
529 		return -ENOMEM;
530 	}
531 	memcpy(&ctx->drv_opts, drv_opts, sizeof(*drv_opts));
532 	memcpy(&ctx->bdev_opts, bdev_opts, sizeof(*bdev_opts));
533 	ctx->sb = sb;
534 	ctx->calling_thread = spdk_get_thread();
535 	TAILQ_INIT(&ctx->mdns_discovery_entry_ctxs);
536 	/* Even if user did not specify hostnqn, we can still strdup("\0"); */
537 	ctx->hostnqn = strdup(ctx->drv_opts.hostnqn);
538 	if (ctx->hostnqn == NULL) {
539 		SPDK_ERRLOG("Error creating mDNS discovery ctx hostnqn for service: %s\n", svcname);
540 		free_mdns_discovery_ctx(ctx);
541 		return -ENOMEM;
542 	}
543 	/* Start the poller for the Avahi client browser in g_bdev_nvme_init_thread */
544 	spdk_thread_send_msg(g_bdev_nvme_init_thread, start_mdns_discovery_poller, ctx);
545 	return 0;
546 }
547 
548 static void
549 mdns_stop_discovery_entry(struct mdns_discovery_ctx *ctx)
550 {
551 	struct mdns_discovery_entry_ctx *entry_ctx = NULL;
552 
553 	assert(ctx);
554 
555 	TAILQ_FOREACH(entry_ctx, &ctx->mdns_discovery_entry_ctxs, tailq) {
556 		bdev_nvme_stop_discovery(entry_ctx->name, NULL, NULL);
557 	}
558 }
559 
560 int
561 bdev_nvme_stop_mdns_discovery(const char *name)
562 {
563 	struct mdns_discovery_ctx *ctx;
564 
565 	assert(name);
566 	TAILQ_FOREACH(ctx, &g_mdns_discovery_ctxs, tailq) {
567 		if (strcmp(name, ctx->name) == 0) {
568 			if (ctx->stop) {
569 				return -EALREADY;
570 			}
571 			/* set stop to true to stop the mdns poller instance */
572 			ctx->stop = true;
573 			mdns_stop_discovery_entry(ctx);
574 			return 0;
575 		}
576 	}
577 
578 	return -ENOENT;
579 }
580 
581 void
582 bdev_nvme_get_mdns_discovery_info(struct spdk_jsonrpc_request *request)
583 {
584 	struct mdns_discovery_ctx *ctx;
585 	struct mdns_discovery_entry_ctx *entry_ctx;
586 	struct spdk_json_write_ctx *w;
587 
588 	w = spdk_jsonrpc_begin_result(request);
589 	spdk_json_write_array_begin(w);
590 	TAILQ_FOREACH(ctx, &g_mdns_discovery_ctxs, tailq) {
591 		spdk_json_write_object_begin(w);
592 		spdk_json_write_named_string(w, "name", ctx->name);
593 		spdk_json_write_named_string(w, "svcname", ctx->svcname);
594 
595 		spdk_json_write_named_array_begin(w, "referrals");
596 		TAILQ_FOREACH(entry_ctx, &ctx->mdns_discovery_entry_ctxs, tailq) {
597 			spdk_json_write_object_begin(w);
598 			spdk_json_write_named_string(w, "name", entry_ctx->name);
599 			spdk_json_write_named_object_begin(w, "trid");
600 			nvme_bdev_dump_trid_json(&entry_ctx->trid, w);
601 			spdk_json_write_object_end(w);
602 			spdk_json_write_object_end(w);
603 		}
604 		spdk_json_write_array_end(w);
605 
606 		spdk_json_write_object_end(w);
607 	}
608 	spdk_json_write_array_end(w);
609 	spdk_jsonrpc_end_result(request, w);
610 }
611 
612 void
613 bdev_nvme_mdns_discovery_config_json(struct spdk_json_write_ctx *w)
614 {
615 	struct mdns_discovery_ctx *ctx;
616 
617 	TAILQ_FOREACH(ctx, &g_mdns_discovery_ctxs, tailq) {
618 		spdk_json_write_object_begin(w);
619 
620 		spdk_json_write_named_string(w, "method", "bdev_nvme_start_mdns_discovery");
621 
622 		spdk_json_write_named_object_begin(w, "params");
623 		spdk_json_write_named_string(w, "name", ctx->name);
624 		spdk_json_write_named_string(w, "svcname", ctx->svcname);
625 		spdk_json_write_named_string(w, "hostnqn", ctx->hostnqn);
626 		spdk_json_write_object_end(w);
627 
628 		spdk_json_write_object_end(w);
629 	}
630 }
631 
632 #else /* SPDK_CONFIG_AVAHI */
633 
634 int
635 bdev_nvme_start_mdns_discovery(const char *base_name,
636 			       const char *svcname,
637 			       struct spdk_nvme_ctrlr_opts *drv_opts,
638 			       struct nvme_ctrlr_opts *bdev_opts)
639 {
640 	SPDK_ERRLOG("spdk not built with --with-avahi option\n");
641 	return -ENOTSUP;
642 }
643 
644 int
645 bdev_nvme_stop_mdns_discovery(const char *name)
646 {
647 	SPDK_ERRLOG("spdk not built with --with-avahi option\n");
648 	return -ENOTSUP;
649 }
650 
651 void
652 bdev_nvme_get_mdns_discovery_info(struct spdk_jsonrpc_request *request)
653 {
654 	SPDK_ERRLOG("spdk not built with --with-avahi option\n");
655 	spdk_jsonrpc_send_error_response(request, -ENOTSUP, spdk_strerror(ENOTSUP));
656 }
657 
658 void
659 bdev_nvme_mdns_discovery_config_json(struct spdk_json_write_ctx *w)
660 {
661 	/* Empty function to be invoked, when SPDK is built without --with-avahi */
662 }
663 
664 #endif
665