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 continue; 153 } 154 } 155 156 avahi_entry_group_commit(g_avahi_entry_group); 157 158 return 0; 159 } 160 161 static void 162 publish_client_new_callback(AvahiClient *client, AvahiClientState avahi_state, 163 AVAHI_GCC_UNUSED void *user_data) 164 { 165 int rc; 166 struct mdns_publish_ctx *publish_ctx = user_data; 167 168 switch (avahi_state) { 169 case AVAHI_CLIENT_S_RUNNING: 170 rc = publish_pull_registration_request(client, publish_ctx); 171 if (rc) { 172 nvmf_ctx_stop_mdns_prr(publish_ctx); 173 } 174 break; 175 case AVAHI_CLIENT_CONNECTING: 176 SPDK_INFOLOG(nvmf, "Avahi client waiting for avahi-daemon"); 177 break; 178 case AVAHI_CLIENT_S_REGISTERING: 179 SPDK_INFOLOG(nvmf, "Avahi client registering service"); 180 break; 181 case AVAHI_CLIENT_FAILURE: 182 SPDK_ERRLOG("Server connection failure: %s\n", avahi_strerror(avahi_client_errno(client))); 183 nvmf_ctx_stop_mdns_prr(publish_ctx); 184 break; 185 case AVAHI_CLIENT_S_COLLISION: 186 SPDK_ERRLOG("Avahi client name is already used in the mDNS"); 187 nvmf_ctx_stop_mdns_prr(publish_ctx); 188 break; 189 default: 190 SPDK_ERRLOG("Avahi client is in unsupported state"); 191 break; 192 } 193 } 194 195 int 196 nvmf_publish_mdns_prr(struct spdk_nvmf_tgt *tgt) 197 { 198 int error; 199 struct mdns_publish_ctx *publish_ctx = NULL; 200 struct spdk_nvmf_subsystem *subsystem = NULL; 201 202 if (g_mdns_publish_ctx != NULL) { 203 if (g_mdns_publish_ctx->tgt == tgt) { 204 SPDK_ERRLOG("mDNS server is already running on target %s.\n", tgt->name); 205 return -EEXIST; 206 } 207 SPDK_ERRLOG("mDNS server does not support publishing multiple targets simultaneously."); 208 return -EINVAL; 209 } 210 211 subsystem = spdk_nvmf_tgt_find_subsystem(tgt, SPDK_NVMF_DISCOVERY_NQN); 212 if (TAILQ_EMPTY(&subsystem->listeners)) { 213 SPDK_ERRLOG("Discovery subsystem has no listeners.\n"); 214 return -EINVAL; 215 } 216 217 publish_ctx = calloc(1, sizeof(*publish_ctx)); 218 if (publish_ctx == NULL) { 219 SPDK_ERRLOG("Error creating mDNS publish ctx\n"); 220 return -ENOMEM; 221 } 222 publish_ctx->subsystem = subsystem; 223 publish_ctx->tgt = tgt; 224 /* Allocate main loop object */ 225 g_avahi_publish_simple_poll = avahi_simple_poll_new(); 226 if (g_avahi_publish_simple_poll == NULL) { 227 SPDK_ERRLOG("Failed to create poll object for mDNS publish.\n"); 228 nvmf_avahi_publish_destroy(publish_ctx); 229 return -ENOMEM; 230 } 231 232 assert(g_avahi_publish_client == NULL); 233 234 /* Allocate a new client */ 235 g_avahi_publish_client = avahi_client_new(avahi_simple_poll_get(g_avahi_publish_simple_poll), 236 0, publish_client_new_callback, publish_ctx, &error); 237 /* Check whether creating the client object succeeded */ 238 if (g_avahi_publish_client == NULL) { 239 SPDK_ERRLOG("Failed to create mDNS client Error: %s\n", avahi_strerror(error)); 240 nvmf_avahi_publish_destroy(publish_ctx); 241 return -ENOMEM; 242 } 243 244 g_mdns_publish_ctx = publish_ctx; 245 publish_ctx->poller = SPDK_POLLER_REGISTER(nvmf_avahi_publish_iterate, publish_ctx, 100 * 1000); 246 return 0; 247 } 248 249 #endif 250