xref: /spdk/lib/nvmf/mdns_server.c (revision bf30e09abe1667ae2769aa367cde39c550bcac00)
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 	struct spdk_poller		*poller;
32 	struct spdk_nvmf_subsystem	*subsystem;
33 	struct spdk_nvmf_tgt		*tgt;
34 };
35 
36 static struct mdns_publish_ctx *g_mdns_publish_ctx = NULL;
37 
38 static void
39 nvmf_avahi_publish_destroy(struct mdns_publish_ctx *ctx)
40 {
41 	if (g_avahi_entry_group) {
42 		avahi_entry_group_free(g_avahi_entry_group);
43 		g_avahi_entry_group = NULL;
44 	}
45 
46 	if (g_avahi_publish_client) {
47 		avahi_client_free(g_avahi_publish_client);
48 		g_avahi_publish_client = NULL;
49 	}
50 
51 	if (g_avahi_publish_simple_poll) {
52 		avahi_simple_poll_free(g_avahi_publish_simple_poll);
53 		g_avahi_publish_simple_poll = NULL;
54 	}
55 
56 	g_mdns_publish_ctx = NULL;
57 	free(ctx);
58 }
59 
60 static int
61 nvmf_avahi_publish_iterate(void *arg)
62 {
63 	struct mdns_publish_ctx *ctx = arg;
64 	int rc;
65 
66 	if (ctx == NULL) {
67 		assert(false);
68 		return SPDK_POLLER_IDLE;
69 	}
70 
71 	rc = avahi_simple_poll_iterate(g_avahi_publish_simple_poll, 0);
72 	if (rc && rc != -EAGAIN) {
73 		SPDK_ERRLOG("avahi publish poll returned error\n");
74 		spdk_poller_unregister(&ctx->poller);
75 		nvmf_avahi_publish_destroy(ctx);
76 		return SPDK_POLLER_BUSY;
77 	}
78 
79 	return SPDK_POLLER_BUSY;
80 }
81 
82 static void
83 nvmf_ctx_stop_mdns_prr(struct mdns_publish_ctx *ctx)
84 {
85 	SPDK_INFOLOG(nvmf, "Stopping avahi publish poller\n");
86 	spdk_poller_unregister(&ctx->poller);
87 	nvmf_avahi_publish_destroy(ctx);
88 }
89 
90 static bool
91 nvmf_tgt_is_mdns_running(struct spdk_nvmf_tgt *tgt)
92 {
93 	if (g_mdns_publish_ctx && g_mdns_publish_ctx->tgt == tgt) {
94 		return true;
95 	}
96 	return false;
97 }
98 
99 void
100 nvmf_tgt_stop_mdns_prr(struct spdk_nvmf_tgt *tgt)
101 {
102 	if (nvmf_tgt_is_mdns_running(tgt) == true) {
103 		nvmf_ctx_stop_mdns_prr(g_mdns_publish_ctx);
104 		return;
105 	}
106 }
107 
108 static void
109 avahi_entry_group_add_listeners(AvahiEntryGroup *avahi_entry_group,
110 				struct spdk_nvmf_subsystem *subsystem)
111 {
112 	struct spdk_nvmf_subsystem_listener *listener;
113 	const char *name_base = "spdk";
114 	const char *type_base = "_nvme-disc";
115 	const char *domain = "local";
116 	char *protocol;
117 	char name[NVMF_MAX_DNS_NAME_LENGTH];
118 	char type[NVMF_MAX_DNS_NAME_LENGTH];
119 	char txt_protocol[NVMF_MAX_DNS_NAME_LENGTH];
120 	char txt_nqn[NVMF_MAX_DNS_NAME_LENGTH];
121 	AvahiStringList *txt = NULL;
122 	uint16_t port;
123 	uint16_t id = 0;
124 
125 	TAILQ_FOREACH(listener, &subsystem->listeners, link) {
126 		if (listener->trid->trtype == SPDK_NVME_TRANSPORT_TCP) {
127 			protocol = "tcp";
128 		} else if (listener->trid->trtype == SPDK_NVME_TRANSPORT_RDMA) {
129 			SPDK_ERRLOG("Current SPDK doesn't distinguish RoCE(udp) and iWARP(tcp). Skip adding listener id %d to avahi entry",
130 				    listener->id);
131 			continue;
132 		} else {
133 			SPDK_ERRLOG("mDNS PRR does not support trtype %d", listener->trid->trtype);
134 			continue;
135 		}
136 
137 		snprintf(type, sizeof(type), "%s._%s", type_base, protocol);
138 		snprintf(name, sizeof(name), "%s%d", name_base, id++);
139 		snprintf(txt_protocol, sizeof(txt_protocol), "p=%s", protocol);
140 		snprintf(txt_nqn, sizeof(txt_nqn), "nqn=%s", SPDK_NVMF_DISCOVERY_NQN);
141 		txt = avahi_string_list_add(txt, txt_protocol);
142 		txt = avahi_string_list_add(txt, txt_nqn);
143 		port = spdk_strtol(listener->trid->trsvcid, 10);
144 
145 		if (avahi_entry_group_add_service_strlst(avahi_entry_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
146 				0, name, type, domain, NULL, port, txt) < 0) {
147 			SPDK_ERRLOG("Failed to add avahi service name: %s, type: %s, domain: %s, port: %d",
148 				    name, type, domain, port);
149 		}
150 		avahi_string_list_free(txt);
151 		txt = NULL;
152 	}
153 
154 	avahi_entry_group_commit(avahi_entry_group);
155 }
156 
157 int
158 nvmf_tgt_update_mdns_prr(struct spdk_nvmf_tgt *tgt)
159 {
160 	int rc;
161 
162 	if (nvmf_tgt_is_mdns_running(tgt) == false || g_avahi_entry_group == NULL) {
163 		SPDK_INFOLOG(nvmf,
164 			     "nvmf_tgt_update_mdns_prr is only supported when mDNS servier is running on target\n");
165 		return 0;
166 	}
167 
168 	rc = avahi_entry_group_reset(g_avahi_entry_group);
169 	if (rc) {
170 		SPDK_ERRLOG("Failed to reset avahi_entry_group");
171 		return -EINVAL;
172 	}
173 
174 	avahi_entry_group_add_listeners(g_avahi_entry_group, g_mdns_publish_ctx->subsystem);
175 
176 	return 0;
177 }
178 
179 static int
180 publish_pull_registration_request(AvahiClient *client, struct mdns_publish_ctx *publish_ctx)
181 {
182 	struct spdk_nvmf_subsystem *subsystem = publish_ctx->subsystem;
183 
184 	if (g_avahi_entry_group != NULL) {
185 		return 0;
186 	}
187 
188 	g_avahi_entry_group = avahi_entry_group_new(client, NULL, NULL);
189 	if (g_avahi_entry_group == NULL) {
190 		SPDK_ERRLOG("avahi_entry_group_new failure: %s\n", avahi_strerror(avahi_client_errno(client)));
191 		return -1;
192 	}
193 
194 	avahi_entry_group_add_listeners(g_avahi_entry_group, subsystem);
195 
196 	return 0;
197 }
198 
199 static void
200 publish_client_new_callback(AvahiClient *client, AvahiClientState avahi_state,
201 			    AVAHI_GCC_UNUSED void *user_data)
202 {
203 	int rc;
204 	struct mdns_publish_ctx *publish_ctx = user_data;
205 
206 	switch (avahi_state) {
207 	case AVAHI_CLIENT_S_RUNNING:
208 		rc = publish_pull_registration_request(client, publish_ctx);
209 		if (rc) {
210 			nvmf_ctx_stop_mdns_prr(publish_ctx);
211 		}
212 		break;
213 	case AVAHI_CLIENT_CONNECTING:
214 		SPDK_INFOLOG(nvmf, "Avahi client waiting for avahi-daemon");
215 		break;
216 	case AVAHI_CLIENT_S_REGISTERING:
217 		SPDK_INFOLOG(nvmf, "Avahi client registering service");
218 		break;
219 	case AVAHI_CLIENT_FAILURE:
220 		SPDK_ERRLOG("Server connection failure: %s\n", avahi_strerror(avahi_client_errno(client)));
221 		nvmf_ctx_stop_mdns_prr(publish_ctx);
222 		break;
223 	case AVAHI_CLIENT_S_COLLISION:
224 		SPDK_ERRLOG("Avahi client name is already used in the mDNS");
225 		nvmf_ctx_stop_mdns_prr(publish_ctx);
226 		break;
227 	default:
228 		SPDK_ERRLOG("Avahi client is in unsupported state");
229 		break;
230 	}
231 }
232 
233 int
234 nvmf_publish_mdns_prr(struct spdk_nvmf_tgt *tgt)
235 {
236 	int error;
237 	struct mdns_publish_ctx *publish_ctx = NULL;
238 	struct spdk_nvmf_subsystem *subsystem = NULL;
239 
240 	if (g_mdns_publish_ctx != NULL) {
241 		if (g_mdns_publish_ctx->tgt == tgt) {
242 			SPDK_ERRLOG("mDNS server is already running on target %s.\n", tgt->name);
243 			return -EEXIST;
244 		}
245 		SPDK_ERRLOG("mDNS server does not support publishing multiple targets simultaneously.");
246 		return -EINVAL;
247 	}
248 
249 	subsystem = spdk_nvmf_tgt_find_subsystem(tgt, SPDK_NVMF_DISCOVERY_NQN);
250 	if (TAILQ_EMPTY(&subsystem->listeners)) {
251 		SPDK_ERRLOG("Discovery subsystem has no listeners.\n");
252 		return -EINVAL;
253 	}
254 
255 	publish_ctx = calloc(1, sizeof(*publish_ctx));
256 	if (publish_ctx == NULL) {
257 		SPDK_ERRLOG("Error creating mDNS publish ctx\n");
258 		return -ENOMEM;
259 	}
260 	publish_ctx->subsystem = subsystem;
261 	publish_ctx->tgt = tgt;
262 	/* Allocate main loop object */
263 	g_avahi_publish_simple_poll = avahi_simple_poll_new();
264 	if (g_avahi_publish_simple_poll == NULL) {
265 		SPDK_ERRLOG("Failed to create poll object for mDNS publish.\n");
266 		nvmf_avahi_publish_destroy(publish_ctx);
267 		return -ENOMEM;
268 	}
269 
270 	assert(g_avahi_publish_client == NULL);
271 
272 	/* Allocate a new client */
273 	g_avahi_publish_client = avahi_client_new(avahi_simple_poll_get(g_avahi_publish_simple_poll),
274 				 0, publish_client_new_callback, publish_ctx, &error);
275 	/* Check whether creating the client object succeeded */
276 	if (g_avahi_publish_client == NULL) {
277 		SPDK_ERRLOG("Failed to create mDNS client Error: %s\n", avahi_strerror(error));
278 		nvmf_avahi_publish_destroy(publish_ctx);
279 		return -ENOMEM;
280 	}
281 
282 	g_mdns_publish_ctx = publish_ctx;
283 	publish_ctx->poller = SPDK_POLLER_REGISTER(nvmf_avahi_publish_iterate, publish_ctx, 100 * 1000);
284 	return 0;
285 }
286 #endif
287