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