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