xref: /spdk/lib/nvmf/mdns_server.c (revision a3f9811ff22e699f36824cdfdbafdd4ed23b6913)
1 /*  SPDX-License-Identifier: BSD-3-Clause
2  *  Copyright (c) 2022 Dell Inc, or its subsidiaries.
3  *  Copyright (c) 2024 Samsung Electronics Co., Ltd. All rights reserved.
4  *  All rights reserved.
5  */
6 
7 #include "spdk/stdinc.h"
8 #include "spdk/env.h"
9 #include "spdk/thread.h"
10 #include "nvmf_internal.h"
11 #include "spdk/log.h"
12 #include "spdk/config.h"
13 #include "spdk/nvme.h"
14 #include "spdk/string.h"
15 
16 #ifdef SPDK_CONFIG_AVAHI
17 #include <avahi-client/client.h>
18 #include <avahi-client/publish.h>
19 #include <avahi-client/lookup.h>
20 #include <avahi-common/simple-watch.h>
21 #include <avahi-common/malloc.h>
22 #include <avahi-common/error.h>
23 
24 #define NVMF_MAX_DNS_NAME_LENGTH 255
25 
26 static AvahiSimplePoll *g_avahi_publish_simple_poll = NULL;
27 static AvahiClient *g_avahi_publish_client = NULL;
28 static AvahiEntryGroup *g_avahi_entry_group = NULL;
29 
30 struct mdns_publish_ctx {
31 	bool				stop;
32 	struct spdk_poller		*poller;
33 	struct spdk_nvmf_subsystem	*subsystem;
34 	struct spdk_nvmf_tgt		*tgt;
35 };
36 
37 static struct mdns_publish_ctx *g_mdns_publish_ctx = NULL;
38 
39 static void
40 nvmf_avahi_publish_destroy(struct mdns_publish_ctx *ctx)
41 {
42 	if (g_avahi_entry_group) {
43 		avahi_entry_group_free(g_avahi_entry_group);
44 		g_avahi_entry_group = NULL;
45 	}
46 
47 	if (g_avahi_publish_client) {
48 		avahi_client_free(g_avahi_publish_client);
49 		g_avahi_publish_client = NULL;
50 	}
51 
52 	if (g_avahi_publish_simple_poll) {
53 		avahi_simple_poll_free(g_avahi_publish_simple_poll);
54 		g_avahi_publish_simple_poll = NULL;
55 	}
56 
57 	g_mdns_publish_ctx = NULL;
58 	free(ctx);
59 }
60 
61 static int
62 nvmf_avahi_publish_iterate(void *arg)
63 {
64 	struct mdns_publish_ctx *ctx = arg;
65 	int rc;
66 
67 	if (ctx == NULL) {
68 		assert(false);
69 		return SPDK_POLLER_IDLE;
70 	}
71 
72 	if (ctx->stop) {
73 		SPDK_INFOLOG(nvmf, "Stopping avahi publish poller\n");
74 		spdk_poller_unregister(&ctx->poller);
75 		nvmf_avahi_publish_destroy(ctx);
76 		return SPDK_POLLER_BUSY;
77 	}
78 
79 	rc = avahi_simple_poll_iterate(g_avahi_publish_simple_poll, 0);
80 	if (rc && rc != -EAGAIN) {
81 		SPDK_ERRLOG("avahi publish poll returned error\n");
82 		spdk_poller_unregister(&ctx->poller);
83 		nvmf_avahi_publish_destroy(ctx);
84 		return SPDK_POLLER_BUSY;
85 	}
86 
87 	return SPDK_POLLER_BUSY;
88 }
89 
90 static void
91 nvmf_ctx_stop_mdns_prr(struct mdns_publish_ctx *ctx)
92 {
93 	ctx->stop = true;
94 }
95 
96 void
97 nvmf_tgt_stop_mdns_prr(struct spdk_nvmf_tgt *tgt)
98 {
99 	if (g_mdns_publish_ctx && g_mdns_publish_ctx->tgt == tgt) {
100 		nvmf_ctx_stop_mdns_prr(g_mdns_publish_ctx);
101 		return;
102 	}
103 
104 	SPDK_ERRLOG("Failed to stop mDNS PRR. It is not running on target %s.\n", tgt->name);
105 }
106 
107 static int
108 publish_pull_registration_request(AvahiClient *client, struct mdns_publish_ctx *publish_ctx)
109 {
110 	struct spdk_nvmf_subsystem *subsystem = publish_ctx->subsystem;
111 	struct spdk_nvmf_subsystem_listener *listener;
112 	const char *name_base = "spdk";
113 	const char *type_base = "_nvme-disc";
114 	const char *domain = "local";
115 	char *protocol;
116 	char name[NVMF_MAX_DNS_NAME_LENGTH];
117 	char type[NVMF_MAX_DNS_NAME_LENGTH];
118 	char txt_protocol[NVMF_MAX_DNS_NAME_LENGTH];
119 	char txt_nqn[NVMF_MAX_DNS_NAME_LENGTH];
120 	AvahiStringList *txt = NULL;
121 	uint16_t port;
122 	uint16_t id = 0;
123 
124 	if (g_avahi_entry_group != NULL) {
125 		return 0;
126 	}
127 
128 	g_avahi_entry_group = avahi_entry_group_new(client, NULL, NULL);
129 	if (g_avahi_entry_group == NULL) {
130 		SPDK_ERRLOG("avahi_entry_group_new failure: %s\n", avahi_strerror(avahi_client_errno(client)));
131 		return -1;
132 	}
133 
134 	TAILQ_FOREACH(listener, &subsystem->listeners, link) {
135 		if (listener->trid->trtype == SPDK_NVME_TRANSPORT_TCP) {
136 			protocol = "tcp";
137 		} else if (listener->trid->trtype == SPDK_NVME_TRANSPORT_RDMA) {
138 			SPDK_ERRLOG("Current SPDK doesn't distinguish RoCE(udp) and iWARP(tcp). Skip adding listener id %d to avahi entry",
139 				    listener->id);
140 			continue;
141 		} else {
142 			SPDK_ERRLOG("mDNS PRR does not support trtype %d", listener->trid->trtype);
143 			continue;
144 		}
145 
146 		snprintf(type, sizeof(type), "%s._%s", type_base, protocol);
147 		snprintf(name, sizeof(name), "%s%d", name_base, id++);
148 		snprintf(txt_protocol, sizeof(txt_protocol), "p=%s", protocol);
149 		snprintf(txt_nqn, sizeof(txt_nqn), "nqn=%s", SPDK_NVMF_DISCOVERY_NQN);
150 		txt = avahi_string_list_add(txt, txt_protocol);
151 		txt = avahi_string_list_add(txt, txt_nqn);
152 		port = spdk_strtol(listener->trid->trsvcid, 10);
153 
154 		if (avahi_entry_group_add_service_strlst(g_avahi_entry_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
155 				0, name, type, domain, NULL, port, txt) < 0) {
156 			SPDK_ERRLOG("Failed to add avahi service name: %s, type: %s, domain: %s, port: %d, error: %s\n",
157 				    name, type, domain, port, avahi_strerror(avahi_client_errno(client)));
158 			continue;
159 		}
160 	}
161 
162 	avahi_entry_group_commit(g_avahi_entry_group);
163 
164 	return 0;
165 }
166 
167 static void
168 publish_client_new_callback(AvahiClient *client, AvahiClientState avahi_state,
169 			    AVAHI_GCC_UNUSED void *user_data)
170 {
171 	int rc;
172 	struct mdns_publish_ctx *publish_ctx = user_data;
173 
174 	switch (avahi_state) {
175 	case AVAHI_CLIENT_S_RUNNING:
176 		rc = publish_pull_registration_request(client, publish_ctx);
177 		if (rc) {
178 			nvmf_ctx_stop_mdns_prr(publish_ctx);
179 		}
180 		break;
181 	case AVAHI_CLIENT_CONNECTING:
182 		SPDK_INFOLOG(nvmf, "Avahi client waiting for avahi-daemon");
183 		break;
184 	case AVAHI_CLIENT_S_REGISTERING:
185 		SPDK_INFOLOG(nvmf, "Avahi client registering service");
186 		break;
187 	case AVAHI_CLIENT_FAILURE:
188 		SPDK_ERRLOG("Server connection failure: %s\n", avahi_strerror(avahi_client_errno(client)));
189 		nvmf_ctx_stop_mdns_prr(publish_ctx);
190 		break;
191 	case AVAHI_CLIENT_S_COLLISION:
192 		SPDK_ERRLOG("Avahi client name is already used in the mDNS");
193 		nvmf_ctx_stop_mdns_prr(publish_ctx);
194 		break;
195 	default:
196 		SPDK_ERRLOG("Avahi client is in unsupported state");
197 		break;
198 	}
199 }
200 
201 int
202 nvmf_publish_mdns_prr(struct spdk_nvmf_tgt *tgt)
203 {
204 	int error;
205 	struct mdns_publish_ctx *publish_ctx = NULL;
206 	struct spdk_nvmf_subsystem *subsystem = NULL;
207 
208 	if (g_mdns_publish_ctx != NULL) {
209 		if (g_mdns_publish_ctx->tgt == tgt) {
210 			SPDK_ERRLOG("mDNS server is already running on target %s.\n", tgt->name);
211 			return -EEXIST;
212 		}
213 		SPDK_ERRLOG("mDNS server does not support publishing multiple targets simultaneously.");
214 		return -EINVAL;
215 	}
216 
217 	subsystem = spdk_nvmf_tgt_find_subsystem(tgt, SPDK_NVMF_DISCOVERY_NQN);
218 	if (TAILQ_EMPTY(&subsystem->listeners)) {
219 		SPDK_ERRLOG("Discovery subsystem has no listeners.\n");
220 		return -EINVAL;
221 	}
222 
223 	publish_ctx = calloc(1, sizeof(*publish_ctx));
224 	if (publish_ctx == NULL) {
225 		SPDK_ERRLOG("Error creating mDNS publish ctx\n");
226 		return -ENOMEM;
227 	}
228 	publish_ctx->subsystem = subsystem;
229 	publish_ctx->tgt = tgt;
230 	/* Allocate main loop object */
231 	g_avahi_publish_simple_poll = avahi_simple_poll_new();
232 	if (g_avahi_publish_simple_poll == NULL) {
233 		SPDK_ERRLOG("Failed to create poll object for mDNS publish.\n");
234 		nvmf_avahi_publish_destroy(publish_ctx);
235 		return -ENOMEM;
236 	}
237 
238 	assert(g_avahi_publish_client == NULL);
239 
240 	/* Allocate a new client */
241 	g_avahi_publish_client = avahi_client_new(avahi_simple_poll_get(g_avahi_publish_simple_poll),
242 				 0, publish_client_new_callback, publish_ctx, &error);
243 	/* Check whether creating the client object succeeded */
244 	if (g_avahi_publish_client == NULL) {
245 		SPDK_ERRLOG("Failed to create mDNS client Error: %s\n", avahi_strerror(error));
246 		nvmf_avahi_publish_destroy(publish_ctx);
247 		return -ENOMEM;
248 	}
249 
250 	g_mdns_publish_ctx = publish_ctx;
251 	publish_ctx->poller = SPDK_POLLER_REGISTER(nvmf_avahi_publish_iterate, publish_ctx, 100 * 1000);
252 	return 0;
253 }
254 
255 #endif
256