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