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 void 91 nvmf_tgt_stop_mdns_prr(struct spdk_nvmf_tgt *tgt) 92 { 93 if (g_mdns_publish_ctx && g_mdns_publish_ctx->tgt == tgt) { 94 nvmf_ctx_stop_mdns_prr(g_mdns_publish_ctx); 95 return; 96 } 97 98 SPDK_ERRLOG("Failed to stop mDNS PRR. It is not running on target %s.\n", tgt->name); 99 } 100 101 static void 102 avahi_entry_group_add_listeners(AvahiEntryGroup *avahi_entry_group, 103 struct spdk_nvmf_subsystem *subsystem) 104 { 105 struct spdk_nvmf_subsystem_listener *listener; 106 const char *name_base = "spdk"; 107 const char *type_base = "_nvme-disc"; 108 const char *domain = "local"; 109 char *protocol; 110 char name[NVMF_MAX_DNS_NAME_LENGTH]; 111 char type[NVMF_MAX_DNS_NAME_LENGTH]; 112 char txt_protocol[NVMF_MAX_DNS_NAME_LENGTH]; 113 char txt_nqn[NVMF_MAX_DNS_NAME_LENGTH]; 114 AvahiStringList *txt = NULL; 115 uint16_t port; 116 uint16_t id = 0; 117 118 TAILQ_FOREACH(listener, &subsystem->listeners, link) { 119 if (listener->trid->trtype == SPDK_NVME_TRANSPORT_TCP) { 120 protocol = "tcp"; 121 } else if (listener->trid->trtype == SPDK_NVME_TRANSPORT_RDMA) { 122 SPDK_ERRLOG("Current SPDK doesn't distinguish RoCE(udp) and iWARP(tcp). Skip adding listener id %d to avahi entry", 123 listener->id); 124 continue; 125 } else { 126 SPDK_ERRLOG("mDNS PRR does not support trtype %d", listener->trid->trtype); 127 continue; 128 } 129 130 snprintf(type, sizeof(type), "%s._%s", type_base, protocol); 131 snprintf(name, sizeof(name), "%s%d", name_base, id++); 132 snprintf(txt_protocol, sizeof(txt_protocol), "p=%s", protocol); 133 snprintf(txt_nqn, sizeof(txt_nqn), "nqn=%s", SPDK_NVMF_DISCOVERY_NQN); 134 txt = avahi_string_list_add(txt, txt_protocol); 135 txt = avahi_string_list_add(txt, txt_nqn); 136 port = spdk_strtol(listener->trid->trsvcid, 10); 137 138 if (avahi_entry_group_add_service_strlst(avahi_entry_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 139 0, name, type, domain, NULL, port, txt) < 0) { 140 SPDK_ERRLOG("Failed to add avahi service name: %s, type: %s, domain: %s, port: %d", 141 name, type, domain, port); 142 } 143 avahi_string_list_free(txt); 144 txt = NULL; 145 } 146 147 avahi_entry_group_commit(avahi_entry_group); 148 } 149 150 static int 151 publish_pull_registration_request(AvahiClient *client, struct mdns_publish_ctx *publish_ctx) 152 { 153 struct spdk_nvmf_subsystem *subsystem = publish_ctx->subsystem; 154 155 if (g_avahi_entry_group != NULL) { 156 return 0; 157 } 158 159 g_avahi_entry_group = avahi_entry_group_new(client, NULL, NULL); 160 if (g_avahi_entry_group == NULL) { 161 SPDK_ERRLOG("avahi_entry_group_new failure: %s\n", avahi_strerror(avahi_client_errno(client))); 162 return -1; 163 } 164 165 avahi_entry_group_add_listeners(g_avahi_entry_group, subsystem); 166 167 return 0; 168 } 169 170 static void 171 publish_client_new_callback(AvahiClient *client, AvahiClientState avahi_state, 172 AVAHI_GCC_UNUSED void *user_data) 173 { 174 int rc; 175 struct mdns_publish_ctx *publish_ctx = user_data; 176 177 switch (avahi_state) { 178 case AVAHI_CLIENT_S_RUNNING: 179 rc = publish_pull_registration_request(client, publish_ctx); 180 if (rc) { 181 nvmf_ctx_stop_mdns_prr(publish_ctx); 182 } 183 break; 184 case AVAHI_CLIENT_CONNECTING: 185 SPDK_INFOLOG(nvmf, "Avahi client waiting for avahi-daemon"); 186 break; 187 case AVAHI_CLIENT_S_REGISTERING: 188 SPDK_INFOLOG(nvmf, "Avahi client registering service"); 189 break; 190 case AVAHI_CLIENT_FAILURE: 191 SPDK_ERRLOG("Server connection failure: %s\n", avahi_strerror(avahi_client_errno(client))); 192 nvmf_ctx_stop_mdns_prr(publish_ctx); 193 break; 194 case AVAHI_CLIENT_S_COLLISION: 195 SPDK_ERRLOG("Avahi client name is already used in the mDNS"); 196 nvmf_ctx_stop_mdns_prr(publish_ctx); 197 break; 198 default: 199 SPDK_ERRLOG("Avahi client is in unsupported state"); 200 break; 201 } 202 } 203 204 int 205 nvmf_publish_mdns_prr(struct spdk_nvmf_tgt *tgt) 206 { 207 int error; 208 struct mdns_publish_ctx *publish_ctx = NULL; 209 struct spdk_nvmf_subsystem *subsystem = NULL; 210 211 if (g_mdns_publish_ctx != NULL) { 212 if (g_mdns_publish_ctx->tgt == tgt) { 213 SPDK_ERRLOG("mDNS server is already running on target %s.\n", tgt->name); 214 return -EEXIST; 215 } 216 SPDK_ERRLOG("mDNS server does not support publishing multiple targets simultaneously."); 217 return -EINVAL; 218 } 219 220 subsystem = spdk_nvmf_tgt_find_subsystem(tgt, SPDK_NVMF_DISCOVERY_NQN); 221 if (TAILQ_EMPTY(&subsystem->listeners)) { 222 SPDK_ERRLOG("Discovery subsystem has no listeners.\n"); 223 return -EINVAL; 224 } 225 226 publish_ctx = calloc(1, sizeof(*publish_ctx)); 227 if (publish_ctx == NULL) { 228 SPDK_ERRLOG("Error creating mDNS publish ctx\n"); 229 return -ENOMEM; 230 } 231 publish_ctx->subsystem = subsystem; 232 publish_ctx->tgt = tgt; 233 /* Allocate main loop object */ 234 g_avahi_publish_simple_poll = avahi_simple_poll_new(); 235 if (g_avahi_publish_simple_poll == NULL) { 236 SPDK_ERRLOG("Failed to create poll object for mDNS publish.\n"); 237 nvmf_avahi_publish_destroy(publish_ctx); 238 return -ENOMEM; 239 } 240 241 assert(g_avahi_publish_client == NULL); 242 243 /* Allocate a new client */ 244 g_avahi_publish_client = avahi_client_new(avahi_simple_poll_get(g_avahi_publish_simple_poll), 245 0, publish_client_new_callback, publish_ctx, &error); 246 /* Check whether creating the client object succeeded */ 247 if (g_avahi_publish_client == NULL) { 248 SPDK_ERRLOG("Failed to create mDNS client Error: %s\n", avahi_strerror(error)); 249 nvmf_avahi_publish_destroy(publish_ctx); 250 return -ENOMEM; 251 } 252 253 g_mdns_publish_ctx = publish_ctx; 254 publish_ctx->poller = SPDK_POLLER_REGISTER(nvmf_avahi_publish_iterate, publish_ctx, 100 * 1000); 255 return 0; 256 } 257 258 #endif 259