xref: /spdk/module/bdev/nvme/bdev_mdns_client.c (revision a0d24145bf3d795cf89adc414320b138fae480ab)
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 				avahi_service_resolver_free(r);
329 				return;
330 			}
331 		}
332 		entry_ctx = create_mdns_discovery_entry_ctx(ctx, trid);
333 		TAILQ_INSERT_TAIL(&ctx->mdns_discovery_entry_ctxs, entry_ctx, tailq);
334 		spdk_thread_send_msg(ctx->calling_thread, mdns_bdev_nvme_start_discovery, entry_ctx);
335 		free(trid);
336 		avahi_free(subnqn);
337 		avahi_free(proto);
338 		break;
339 	}
340 	default:
341 		SPDK_ERRLOG("Unknown Avahi resolver event: %d", event);
342 	}
343 	avahi_service_resolver_free(r);
344 }
345 
346 static void
347 mdns_browse_callback(
348 	AvahiServiceBrowser *b,
349 	AvahiIfIndex interface,
350 	AvahiProtocol protocol,
351 	AvahiBrowserEvent event,
352 	const char *name,
353 	const char *type,
354 	const char *domain,
355 	AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
356 	void *userdata)
357 {
358 	AvahiClient *c = userdata;
359 
360 	assert(b);
361 	/* Called whenever a new services becomes available on the LAN or is removed from the LAN */
362 	switch (event) {
363 	case AVAHI_BROWSER_FAILURE:
364 		SPDK_ERRLOG("(Browser) Failure: %s\n",
365 			    avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))));
366 		return;
367 	case AVAHI_BROWSER_NEW:
368 		SPDK_DEBUGLOG(bdev_nvme, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type,
369 			      domain);
370 		/* We ignore the returned resolver object. In the callback
371 		   function we free it. If the server is terminated before
372 		   the callback function is called the server will free
373 		   the resolver for us. */
374 		if (!(avahi_service_resolver_new(c, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0,
375 						 mdns_resolve_callback, c))) {
376 			SPDK_ERRLOG("Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_client_errno(c)));
377 		}
378 		break;
379 	case AVAHI_BROWSER_REMOVE:
380 		SPDK_ERRLOG("(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
381 		/* On remove, we are not doing the automatic cleanup of connections
382 		 * to the targets that were learnt from the CDC, for which remove event has
383 		 * been received. If required, user can clear the connections manually by
384 		 * invoking bdev_nvme_stop_discovery. We can implement the automatic cleanup
385 		 * later, if there is a requirement in the future.
386 		 */
387 		break;
388 	case AVAHI_BROWSER_ALL_FOR_NOW:
389 	case AVAHI_BROWSER_CACHE_EXHAUSTED:
390 		SPDK_INFOLOG(bdev_nvme, "(Browser) %s\n",
391 			     event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
392 		break;
393 	default:
394 		SPDK_ERRLOG("Unknown Avahi browser event: %d", event);
395 	}
396 }
397 
398 static void
399 client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void *userdata)
400 {
401 	assert(c);
402 	/* Called whenever the client or server state changes */
403 	if (state == AVAHI_CLIENT_FAILURE) {
404 		SPDK_ERRLOG("Server connection failure: %s\n", avahi_strerror(avahi_client_errno(c)));
405 	}
406 }
407 
408 static int
409 bdev_nvme_avahi_iterate(void *arg)
410 {
411 	struct mdns_discovery_ctx *ctx = arg;
412 	int rc;
413 
414 	if (ctx->stop) {
415 		SPDK_INFOLOG(bdev_nvme, "Stopping avahi poller for service %s\n", ctx->svcname);
416 		spdk_poller_unregister(&ctx->poller);
417 		TAILQ_REMOVE(&g_mdns_discovery_ctxs, ctx, tailq);
418 		free_mdns_discovery_ctx(ctx);
419 		return SPDK_POLLER_IDLE;
420 	}
421 
422 	if (g_avahi_simple_poll == NULL) {
423 		spdk_poller_unregister(&ctx->poller);
424 		return SPDK_POLLER_IDLE;
425 	}
426 
427 	rc = avahi_simple_poll_iterate(g_avahi_simple_poll, 0);
428 	if (rc && rc != -EAGAIN) {
429 		SPDK_ERRLOG("avahi poll returned error for service: %s/n", ctx->svcname);
430 		return SPDK_POLLER_IDLE;
431 	}
432 
433 	return SPDK_POLLER_BUSY;
434 }
435 
436 static void
437 start_mdns_discovery_poller(void *arg)
438 {
439 	struct mdns_discovery_ctx *ctx = arg;
440 
441 	assert(arg);
442 	TAILQ_INSERT_TAIL(&g_mdns_discovery_ctxs, ctx, tailq);
443 	ctx->poller = SPDK_POLLER_REGISTER(bdev_nvme_avahi_iterate, ctx, 100 * 1000);
444 }
445 
446 int
447 bdev_nvme_start_mdns_discovery(const char *base_name,
448 			       const char *svcname,
449 			       struct spdk_nvme_ctrlr_opts *drv_opts,
450 			       struct nvme_ctrlr_opts *bdev_opts)
451 {
452 	AvahiServiceBrowser *sb = NULL;
453 	int error;
454 	struct mdns_discovery_ctx *ctx;
455 
456 	assert(base_name);
457 	assert(svcname);
458 
459 	TAILQ_FOREACH(ctx, &g_mdns_discovery_ctxs, tailq) {
460 		if (strcmp(ctx->name, base_name) == 0) {
461 			SPDK_ERRLOG("mDNS discovery already running with name %s\n", base_name);
462 			return -EEXIST;
463 		}
464 
465 		if (strcmp(ctx->svcname, svcname) == 0) {
466 			SPDK_ERRLOG("mDNS discovery already running for service %s\n", svcname);
467 			return -EEXIST;
468 		}
469 	}
470 
471 	if (g_avahi_simple_poll == NULL) {
472 
473 		/* Allocate main loop object */
474 		if (!(g_avahi_simple_poll = avahi_simple_poll_new())) {
475 			SPDK_ERRLOG("Failed to create poll object for mDNS discovery for service: %s.\n", svcname);
476 			return -ENOMEM;
477 		}
478 	}
479 
480 	if (g_avahi_client == NULL) {
481 
482 		/* Allocate a new client */
483 		g_avahi_client = avahi_client_new(avahi_simple_poll_get(g_avahi_simple_poll), 0, client_callback,
484 						  NULL, &error);
485 		/* Check whether creating the client object succeeded */
486 		if (!g_avahi_client) {
487 			SPDK_ERRLOG("Failed to create mDNS client for service:%s Error: %s\n", svcname,
488 				    avahi_strerror(error));
489 			return -ENOMEM;
490 		}
491 	}
492 
493 	/* Create the service browser */
494 	if (!(sb = avahi_service_browser_new(g_avahi_client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, svcname,
495 					     NULL, 0, mdns_browse_callback, g_avahi_client))) {
496 		SPDK_ERRLOG("Failed to create service browser for service: %s Error: %s\n", svcname,
497 			    avahi_strerror(avahi_client_errno(g_avahi_client)));
498 		return -ENOMEM;
499 	}
500 
501 	ctx = calloc(1, sizeof(*ctx));
502 	if (ctx == NULL) {
503 		SPDK_ERRLOG("Error creating mDNS discovery ctx for service: %s\n", svcname);
504 		avahi_service_browser_free(sb);
505 		return -ENOMEM;
506 	}
507 
508 	ctx->svcname = strdup(svcname);
509 	if (ctx->svcname == NULL) {
510 		SPDK_ERRLOG("Error creating mDNS discovery ctx svcname for service: %s\n", svcname);
511 		free_mdns_discovery_ctx(ctx);
512 		avahi_service_browser_free(sb);
513 		return -ENOMEM;
514 	}
515 	ctx->name = strdup(base_name);
516 	if (ctx->name == NULL) {
517 		SPDK_ERRLOG("Error creating mDNS discovery ctx name for service: %s\n", svcname);
518 		free_mdns_discovery_ctx(ctx);
519 		avahi_service_browser_free(sb);
520 		return -ENOMEM;
521 	}
522 	memcpy(&ctx->drv_opts, drv_opts, sizeof(*drv_opts));
523 	memcpy(&ctx->bdev_opts, bdev_opts, sizeof(*bdev_opts));
524 	ctx->sb = sb;
525 	ctx->calling_thread = spdk_get_thread();
526 	TAILQ_INIT(&ctx->mdns_discovery_entry_ctxs);
527 	/* Even if user did not specify hostnqn, we can still strdup("\0"); */
528 	ctx->hostnqn = strdup(ctx->drv_opts.hostnqn);
529 	if (ctx->hostnqn == NULL) {
530 		SPDK_ERRLOG("Error creating mDNS discovery ctx hostnqn for service: %s\n", svcname);
531 		free_mdns_discovery_ctx(ctx);
532 		return -ENOMEM;
533 	}
534 	/* Start the poller for the Avahi client browser in g_bdev_nvme_init_thread */
535 	spdk_thread_send_msg(g_bdev_nvme_init_thread, start_mdns_discovery_poller, ctx);
536 	return 0;
537 }
538 
539 static void
540 mdns_stop_discovery_entry(struct mdns_discovery_ctx *ctx)
541 {
542 	struct mdns_discovery_entry_ctx *entry_ctx = NULL;
543 
544 	assert(ctx);
545 
546 	TAILQ_FOREACH(entry_ctx, &ctx->mdns_discovery_entry_ctxs, tailq) {
547 		bdev_nvme_stop_discovery(entry_ctx->name, NULL, NULL);
548 	}
549 }
550 
551 int
552 bdev_nvme_stop_mdns_discovery(const char *name)
553 {
554 	struct mdns_discovery_ctx *ctx;
555 
556 	assert(name);
557 	TAILQ_FOREACH(ctx, &g_mdns_discovery_ctxs, tailq) {
558 		if (strcmp(name, ctx->name) == 0) {
559 			if (ctx->stop) {
560 				return -EALREADY;
561 			}
562 			/* set stop to true to stop the mdns poller instance */
563 			ctx->stop = true;
564 			mdns_stop_discovery_entry(ctx);
565 			return 0;
566 		}
567 	}
568 
569 	return -ENOENT;
570 }
571 
572 void
573 bdev_nvme_get_mdns_discovery_info(struct spdk_jsonrpc_request *request)
574 {
575 	struct mdns_discovery_ctx *ctx;
576 	struct mdns_discovery_entry_ctx *entry_ctx;
577 	struct spdk_json_write_ctx *w;
578 
579 	w = spdk_jsonrpc_begin_result(request);
580 	spdk_json_write_array_begin(w);
581 	TAILQ_FOREACH(ctx, &g_mdns_discovery_ctxs, tailq) {
582 		spdk_json_write_object_begin(w);
583 		spdk_json_write_named_string(w, "name", ctx->name);
584 		spdk_json_write_named_string(w, "svcname", ctx->svcname);
585 
586 		spdk_json_write_named_array_begin(w, "referrals");
587 		TAILQ_FOREACH(entry_ctx, &ctx->mdns_discovery_entry_ctxs, tailq) {
588 			spdk_json_write_object_begin(w);
589 			spdk_json_write_named_string(w, "name", entry_ctx->name);
590 			spdk_json_write_named_object_begin(w, "trid");
591 			nvme_bdev_dump_trid_json(&entry_ctx->trid, w);
592 			spdk_json_write_object_end(w);
593 			spdk_json_write_object_end(w);
594 		}
595 		spdk_json_write_array_end(w);
596 
597 		spdk_json_write_object_end(w);
598 	}
599 	spdk_json_write_array_end(w);
600 	spdk_jsonrpc_end_result(request, w);
601 }
602 
603 void
604 bdev_nvme_mdns_discovery_config_json(struct spdk_json_write_ctx *w)
605 {
606 	struct mdns_discovery_ctx *ctx;
607 
608 	TAILQ_FOREACH(ctx, &g_mdns_discovery_ctxs, tailq) {
609 		spdk_json_write_object_begin(w);
610 
611 		spdk_json_write_named_string(w, "method", "bdev_nvme_start_mdns_discovery");
612 
613 		spdk_json_write_named_object_begin(w, "params");
614 		spdk_json_write_named_string(w, "name", ctx->name);
615 		spdk_json_write_named_string(w, "svcname", ctx->svcname);
616 		spdk_json_write_named_string(w, "hostnqn", ctx->hostnqn);
617 		spdk_json_write_object_end(w);
618 
619 		spdk_json_write_object_end(w);
620 	}
621 }
622 
623 #else /* SPDK_CONFIG_AVAHI */
624 
625 int
626 bdev_nvme_start_mdns_discovery(const char *base_name,
627 			       const char *svcname,
628 			       struct spdk_nvme_ctrlr_opts *drv_opts,
629 			       struct nvme_ctrlr_opts *bdev_opts)
630 {
631 	SPDK_ERRLOG("spdk not built with --with-avahi option\n");
632 	return -ENOTSUP;
633 }
634 
635 int
636 bdev_nvme_stop_mdns_discovery(const char *name)
637 {
638 	SPDK_ERRLOG("spdk not built with --with-avahi option\n");
639 	return -ENOTSUP;
640 }
641 
642 void
643 bdev_nvme_get_mdns_discovery_info(struct spdk_jsonrpc_request *request)
644 {
645 	SPDK_ERRLOG("spdk not built with --with-avahi option\n");
646 	spdk_jsonrpc_send_error_response(request, -ENOTSUP, spdk_strerror(ENOTSUP));
647 }
648 
649 void
650 bdev_nvme_mdns_discovery_config_json(struct spdk_json_write_ctx *w)
651 {
652 	/* Empty function to be invoked, when SPDK is built without --with-avahi */
653 }
654 
655 #endif
656