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