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