| File: | .build-ci/../discoverd/src/mdns.c |
| Warning: | line 865, column 2 Use of memory after it is released |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | // SPDX-License-Identifier: GPL-2.0-or-later | |||
| 2 | /* | |||
| 3 | * This file is part of nvme-cli. | |||
| 4 | * Copyright (c) 2026 Dell Technologies Inc. or its subsidiaries. | |||
| 5 | * | |||
| 6 | * Authors: Martin Belanger <martin.belanger@dell.com> | |||
| 7 | */ | |||
| 8 | ||||
| 9 | #include <arpa/inet.h> | |||
| 10 | #include <errno(*__errno_location ()).h> | |||
| 11 | #include <netinet/in.h> | |||
| 12 | #include <stdbool.h> | |||
| 13 | #include <stdio.h> | |||
| 14 | #include <stdlib.h> | |||
| 15 | #include <string.h> | |||
| 16 | #include <strings.h> | |||
| 17 | #include <sys/epoll.h> | |||
| 18 | #include <sys/socket.h> | |||
| 19 | #include <unistd.h> | |||
| 20 | ||||
| 21 | #include <ccan/array_size/array_size.h> | |||
| 22 | #include <ccan/list/list.h> | |||
| 23 | #include <ccan/str/str.h> | |||
| 24 | #include <daemon-util/log.h> | |||
| 25 | #include <shared/time-util.h> | |||
| 26 | #include <systemd/sd-json.h> | |||
| 27 | #include <systemd/sd-varlink.h> | |||
| 28 | ||||
| 29 | #include "mdns.h" | |||
| 30 | #include "netif.h" | |||
| 31 | ||||
| 32 | #define RESOLVE_VARLINK_ADDRESS"/run/systemd/resolve/io.systemd.Resolve" "/run/systemd/resolve/io.systemd.Resolve" | |||
| 33 | #define RESOLVE_INTERFACE"io.systemd.Resolve" "io.systemd.Resolve" | |||
| 34 | #define VARLINK_GET_DESCRIPTION"org.varlink.service.GetInterfaceDescription" "org.varlink.service.GetInterfaceDescription" | |||
| 35 | #define VARLINK_BROWSE_SERVICES"io.systemd.Resolve.BrowseServices" "io.systemd.Resolve.BrowseServices" | |||
| 36 | #define VARLINK_RESOLVE_SERVICE"io.systemd.Resolve.ResolveService" "io.systemd.Resolve.ResolveService" | |||
| 37 | ||||
| 38 | /* | |||
| 39 | * NVMe Base Spec 2.4 §8.3.1.1.1, Figure 800 (TP8009): TCP and iWARP DCs | |||
| 40 | * advertise "_tcp", RoCE DCs advertise "_udp". Each type needs its own | |||
| 41 | * BrowseServices call. | |||
| 42 | */ | |||
| 43 | static const char * const mdns_service_types[] = { | |||
| 44 | "_nvme-disc._tcp", | |||
| 45 | "_nvme-disc._udp", | |||
| 46 | }; | |||
| 47 | ||||
| 48 | /* | |||
| 49 | * Retry intervals, in seconds, for the TCP reachability check. Some DCs | |||
| 50 | * advertise before their TCP listener is up. An NVMe connect through the | |||
| 51 | * kernel fails at that point, and the nvme driver logs errors. A plain | |||
| 52 | * TCP connect from userspace fails silently. So a tcp endpoint is | |||
| 53 | * reported only after a userspace TCP connect to it succeeds. The last | |||
| 54 | * interval repeats: the check stops only when the advertisement is | |||
| 55 | * withdrawn. | |||
| 56 | */ | |||
| 57 | static const unsigned int tcp_check_retry_sec[] = { | |||
| 58 | 2, 5, 10, 30, 60, 300, 600, | |||
| 59 | }; | |||
| 60 | ||||
| 61 | /* | |||
| 62 | * Restart delay, in seconds, after a browse call fails, e.g. because | |||
| 63 | * systemd-resolved restarted. Doubles on each failed attempt, up to the | |||
| 64 | * maximum. | |||
| 65 | */ | |||
| 66 | #define BROWSE_RESTART_MIN_SEC1 1 | |||
| 67 | #define BROWSE_RESTART_MAX_SEC60 60 | |||
| 68 | ||||
| 69 | /* One resolved connect endpoint for a discovered mDNS service instance. */ | |||
| 70 | struct mdns_endpoint { | |||
| 71 | struct list_node entry; | |||
| 72 | char *traddr; | |||
| 73 | char *trsvcid; | |||
| 74 | }; | |||
| 75 | ||||
| 76 | /* A TCP reachability check for a tcp endpoint not yet reported. */ | |||
| 77 | struct tcp_check { | |||
| 78 | struct list_node entry; | |||
| 79 | struct mdns_browse *br; | |||
| 80 | struct mdns_service *svc; | |||
| 81 | char *traddr; | |||
| 82 | char *trsvcid; | |||
| 83 | struct sockaddr_storage addr; | |||
| 84 | socklen_t addrlen; | |||
| 85 | int fd; // -1 when not connecting | |||
| 86 | sd_event_source *io_source; | |||
| 87 | sd_event_source *retry_timer; | |||
| 88 | size_t retry_idx; // into tcp_check_retry_sec[] | |||
| 89 | bool_Bool ceiling_logged; // for log_info_once() | |||
| 90 | }; | |||
| 91 | ||||
| 92 | /* One discovered mDNS service instance, keyed by name within its browse. */ | |||
| 93 | struct mdns_service { | |||
| 94 | struct list_node entry; | |||
| 95 | char *name; | |||
| 96 | const char *transport; // "tcp" or "rdma", from the TXT record's p= key | |||
| 97 | char *nqn; // TXT record's optional nqn= key, NULL if absent | |||
| 98 | struct list_head endpoints; | |||
| 99 | struct list_head checks; // struct tcp_check | |||
| 100 | }; | |||
| 101 | ||||
| 102 | /* One outstanding ResolveService call for a just-added service instance. */ | |||
| 103 | struct resolve_req { | |||
| 104 | struct list_node entry; | |||
| 105 | struct mdns_browse *br; | |||
| 106 | sd_varlink *link; | |||
| 107 | char *name; | |||
| 108 | }; | |||
| 109 | ||||
| 110 | /* One per-interface BrowseServices subscription. */ | |||
| 111 | struct mdns_browse { | |||
| 112 | struct list_node entry; | |||
| 113 | struct mdns_ctx *mctx; | |||
| 114 | int ifindex; | |||
| 115 | char *ifname; | |||
| 116 | const char *type; // one of mdns_service_types[], static duration | |||
| 117 | sd_varlink *link; // NULL while waiting to restart | |||
| 118 | sd_event_source *restart_timer; | |||
| 119 | unsigned int restart_sec; // next restart delay | |||
| 120 | struct list_head services; // struct mdns_service, resolved instances | |||
| 121 | struct list_head pending; // struct resolve_req, in-flight resolves | |||
| 122 | }; | |||
| 123 | ||||
| 124 | struct mdns_ctx { | |||
| 125 | sd_event *event; | |||
| 126 | struct mdns_callbacks callbacks; | |||
| 127 | void *user_data; | |||
| 128 | struct netif_ctx *nctx; | |||
| 129 | struct list_head browses; // struct mdns_browse, one per candidate iface | |||
| 130 | }; | |||
| 131 | ||||
| 132 | /* @v's "key" member as a string, or NULL if absent or not a string. */ | |||
| 133 | static const char *json_str(sd_json_variant *v, const char *key) | |||
| 134 | { | |||
| 135 | v = sd_json_variant_by_key(v, key); | |||
| 136 | ||||
| 137 | return v ? sd_json_variant_string(v) : NULL((void*)0); | |||
| 138 | } | |||
| 139 | ||||
| 140 | /* @v's "key" member, or NULL if absent or not an array. */ | |||
| 141 | static sd_json_variant *json_array(sd_json_variant *v, const char *key) | |||
| 142 | { | |||
| 143 | v = sd_json_variant_by_key(v, key); | |||
| 144 | ||||
| 145 | return v && sd_json_variant_is_array(v) ? v : NULL((void*)0); | |||
| 146 | } | |||
| 147 | ||||
| 148 | /* | |||
| 149 | * Returns 0 if io.systemd.Resolve has a BrowseServices method, -EOPNOTSUPP | |||
| 150 | * if not, or another negative errno. | |||
| 151 | */ | |||
| 152 | static int resolved_has_browse_services(sd_varlink *link) | |||
| 153 | { | |||
| 154 | sd_json_variant *reply = NULL((void*)0); | |||
| 155 | const char *error_id = NULL((void*)0); | |||
| 156 | const char *text; | |||
| 157 | int r; | |||
| 158 | ||||
| 159 | r = sd_varlink_callbo(link, VARLINK_GET_DESCRIPTION, &reply, &error_id,sd_varlink_callb((link), ("org.varlink.service.GetInterfaceDescription" ), (&reply), (&error_id), _SD_JSON_BUILD_OBJECT_BEGIN , _SD_JSON_BUILD_PAIR, (const char*) { "interface" }, _SD_JSON_BUILD_STRING , (const char*) { "io.systemd.Resolve" }, _SD_JSON_BUILD_OBJECT_END ) | |||
| 160 | SD_JSON_BUILD_PAIR_STRING("interface",sd_varlink_callb((link), ("org.varlink.service.GetInterfaceDescription" ), (&reply), (&error_id), _SD_JSON_BUILD_OBJECT_BEGIN , _SD_JSON_BUILD_PAIR, (const char*) { "interface" }, _SD_JSON_BUILD_STRING , (const char*) { "io.systemd.Resolve" }, _SD_JSON_BUILD_OBJECT_END ) | |||
| 161 | RESOLVE_INTERFACE))sd_varlink_callb((link), ("org.varlink.service.GetInterfaceDescription" ), (&reply), (&error_id), _SD_JSON_BUILD_OBJECT_BEGIN , _SD_JSON_BUILD_PAIR, (const char*) { "interface" }, _SD_JSON_BUILD_STRING , (const char*) { "io.systemd.Resolve" }, _SD_JSON_BUILD_OBJECT_END ); | |||
| 162 | if (r < 0) | |||
| 163 | return r; | |||
| 164 | if (error_id) | |||
| 165 | return -EPROTO71; | |||
| 166 | ||||
| 167 | text = json_str(reply, "description"); | |||
| 168 | if (!text || !strstr(text, "method BrowseServices(")_Generic (0 ? (text) : (void *) 1, const void *: (const char * ) (strstr (text, "method BrowseServices(")), default: strstr ( text, "method BrowseServices("))) | |||
| 169 | return -EOPNOTSUPP95; | |||
| 170 | ||||
| 171 | return 0; | |||
| 172 | } | |||
| 173 | ||||
| 174 | static void mdns_endpoint_free(struct mdns_endpoint *ep) | |||
| 175 | { | |||
| 176 | if (!ep) | |||
| 177 | return; | |||
| 178 | list_del_init(&ep->entry)list_del_init_(&ep->entry, "../discoverd/src/mdns.c" ":" "178"); | |||
| 179 | free(ep->traddr); | |||
| 180 | free(ep->trsvcid); | |||
| 181 | free(ep); | |||
| 182 | } | |||
| 183 | ||||
| 184 | static void tcp_check_free(struct tcp_check *tc) | |||
| 185 | { | |||
| 186 | if (!tc) | |||
| 187 | return; | |||
| 188 | list_del_init(&tc->entry)list_del_init_(&tc->entry, "../discoverd/src/mdns.c" ":" "188"); | |||
| 189 | sd_event_source_unref(tc->io_source); | |||
| 190 | sd_event_source_unref(tc->retry_timer); | |||
| 191 | if (tc->fd >= 0) | |||
| 192 | close(tc->fd); | |||
| 193 | free(tc->traddr); | |||
| 194 | free(tc->trsvcid); | |||
| 195 | free(tc); | |||
| 196 | } | |||
| 197 | ||||
| 198 | static struct mdns_service *service_find(struct mdns_browse *br, | |||
| 199 | const char *name) | |||
| 200 | { | |||
| 201 | struct mdns_service *svc; | |||
| 202 | ||||
| 203 | list_for_each(&br->services, svc, entry)for ((svc) = list_node_to_off_(((void)"../discoverd/src/mdns.c" ":" "203", (&br->services))->n.next, (((__builtin_offsetof (typeof(*svc), entry) + ((typeof(svc->entry) *)0 != (struct list_node *)0))))); list_node_from_off_((void *)(svc), (((__builtin_offsetof (typeof(*svc), entry) + ((typeof(svc->entry) *)0 != (struct list_node *)0))))) != &((&br->services))->n; ( svc) = list_node_to_off_(list_node_from_off_((void *)(svc), ( ((__builtin_offsetof(typeof(*svc), entry) + ((typeof(svc-> entry) *)0 != (struct list_node *)0)))))->next, (((__builtin_offsetof (typeof(*svc), entry) + ((typeof(svc->entry) *)0 != (struct list_node *)0)))))) { | |||
| 204 | if (streq(svc->name, name)(strcmp((svc->name),(name)) == 0)) | |||
| 205 | return svc; | |||
| 206 | } | |||
| 207 | return NULL((void*)0); | |||
| 208 | } | |||
| 209 | ||||
| 210 | /* Cancel the checks, report every endpoint as removed, then free @svc. */ | |||
| 211 | static void service_free(struct mdns_browse *br, struct mdns_service *svc) | |||
| 212 | { | |||
| 213 | struct mdns_endpoint *ep, *next; | |||
| 214 | struct tcp_check *tc, *next_tc; | |||
| 215 | ||||
| 216 | list_for_each_safe(&svc->checks, tc, next_tc, entry)for ((tc) = list_node_to_off_(((void)"../discoverd/src/mdns.c" ":" "216", (&svc->checks))->n.next, (((__builtin_offsetof (typeof(*tc), entry) + ((typeof(tc->entry) *)0 != (struct list_node *)0))))), (next_tc) = list_node_to_off_(list_node_from_off_( (tc), (((__builtin_offsetof(typeof(*tc), entry) + ((typeof(tc ->entry) *)0 != (struct list_node *)0)))))->next, (((__builtin_offsetof (typeof(*tc), entry) + ((typeof(tc->entry) *)0 != (struct list_node *)0))))); list_node_from_off_((tc), (((__builtin_offsetof(typeof (*tc), entry) + ((typeof(tc->entry) *)0 != (struct list_node *)0))))) != &((&svc->checks))->n; (tc) = (next_tc ), (next_tc) = list_node_to_off_(list_node_from_off_((tc), (( (__builtin_offsetof(typeof(*tc), entry) + ((typeof(tc->entry ) *)0 != (struct list_node *)0)))))->next, (((__builtin_offsetof (typeof(*tc), entry) + ((typeof(tc->entry) *)0 != (struct list_node *)0)))))) | |||
| 217 | tcp_check_free(tc); | |||
| 218 | ||||
| 219 | list_for_each_safe(&svc->endpoints, ep, next, entry)for ((ep) = list_node_to_off_(((void)"../discoverd/src/mdns.c" ":" "219", (&svc->endpoints))->n.next, (((__builtin_offsetof (typeof(*ep), entry) + ((typeof(ep->entry) *)0 != (struct list_node *)0))))), (next) = list_node_to_off_(list_node_from_off_((ep ), (((__builtin_offsetof(typeof(*ep), entry) + ((typeof(ep-> entry) *)0 != (struct list_node *)0)))))->next, (((__builtin_offsetof (typeof(*ep), entry) + ((typeof(ep->entry) *)0 != (struct list_node *)0))))); list_node_from_off_((ep), (((__builtin_offsetof(typeof (*ep), entry) + ((typeof(ep->entry) *)0 != (struct list_node *)0))))) != &((&svc->endpoints))->n; (ep) = (next ), (next) = list_node_to_off_(list_node_from_off_((ep), (((__builtin_offsetof (typeof(*ep), entry) + ((typeof(ep->entry) *)0 != (struct list_node *)0)))))->next, (((__builtin_offsetof(typeof(*ep), entry) + ((typeof(ep->entry) *)0 != (struct list_node *)0)))))) { | |||
| 220 | if (br->mctx->callbacks.service_remove) | |||
| 221 | br->mctx->callbacks.service_remove( | |||
| 222 | ep->traddr, ep->trsvcid, svc->transport, | |||
| 223 | svc->nqn, br->ifname, br->ifindex, | |||
| 224 | br->mctx->user_data); | |||
| 225 | mdns_endpoint_free(ep); | |||
| 226 | } | |||
| 227 | ||||
| 228 | list_del_init(&svc->entry)list_del_init_(&svc->entry, "../discoverd/src/mdns.c" ":" "228"); | |||
| 229 | free(svc->name); | |||
| 230 | free(svc->nqn); | |||
| 231 | free(svc); | |||
| 232 | } | |||
| 233 | ||||
| 234 | /* | |||
| 235 | * NVMe Base Spec 2.4 §8.3.1.1.2 (TP8009): the TXT record's p= key gives the | |||
| 236 | * transport. The service type cannot: iWARP is RDMA but advertises "_tcp". | |||
| 237 | * Matched case-insensitively. | |||
| 238 | */ | |||
| 239 | static const char *proto_to_transport(const char *proto) | |||
| 240 | { | |||
| 241 | if (!strcasecmp(proto, "tcp")) | |||
| 242 | return "tcp"; | |||
| 243 | if (!strcasecmp(proto, "roce") || !strcasecmp(proto, "iwarp") || | |||
| 244 | !strcasecmp(proto, "rdma")) | |||
| 245 | return "rdma"; | |||
| 246 | ||||
| 247 | return NULL((void*)0); | |||
| 248 | } | |||
| 249 | ||||
| 250 | /* | |||
| 251 | * Read the TXT record's p= and nqn= keys. Returns -EINVAL if p= is missing | |||
| 252 | * or unknown. *ret_nqn is borrowed from @parameters, or NULL. | |||
| 253 | */ | |||
| 254 | static int resolve_txt(sd_json_variant *parameters, | |||
| 255 | const char **ret_transport, const char **ret_nqn) | |||
| 256 | { | |||
| 257 | sd_json_variant *txt, *item; | |||
| 258 | size_t i, n; | |||
| 259 | ||||
| 260 | *ret_transport = NULL((void*)0); | |||
| 261 | *ret_nqn = NULL((void*)0); | |||
| 262 | ||||
| 263 | txt = json_array(parameters, "txt"); | |||
| 264 | if (!txt) | |||
| 265 | return -EINVAL22; | |||
| 266 | ||||
| 267 | n = sd_json_variant_elements(txt); | |||
| 268 | for (i = 0; i < n; i++) { | |||
| 269 | const char *kv; | |||
| 270 | ||||
| 271 | item = sd_json_variant_by_index(txt, i); | |||
| 272 | kv = sd_json_variant_string(item); | |||
| 273 | if (!kv) | |||
| 274 | continue; | |||
| 275 | ||||
| 276 | if (!*ret_transport && !strncmp(kv, "p=", 2)) | |||
| 277 | *ret_transport = proto_to_transport(kv + 2); | |||
| 278 | else if (!*ret_nqn && !strncmp(kv, "nqn=", 4)) | |||
| 279 | *ret_nqn = kv + 4; | |||
| 280 | } | |||
| 281 | ||||
| 282 | return *ret_transport ? 0 : -EINVAL22; | |||
| 283 | } | |||
| 284 | ||||
| 285 | /* Link @traddr/@trsvcid (ownership taken) into @svc and report it. */ | |||
| 286 | static void report_endpoint(struct mdns_browse *br, struct mdns_service *svc, | |||
| 287 | char *traddr, char *trsvcid) | |||
| 288 | { | |||
| 289 | struct mdns_endpoint *ep; | |||
| 290 | ||||
| 291 | if (!traddr || !trsvcid) { | |||
| 292 | free(traddr); | |||
| 293 | free(trsvcid); | |||
| 294 | return; | |||
| 295 | } | |||
| 296 | ||||
| 297 | ep = calloc(1, sizeof(*ep)); | |||
| 298 | if (!ep) { | |||
| 299 | free(traddr); | |||
| 300 | free(trsvcid); | |||
| 301 | return; | |||
| 302 | } | |||
| 303 | list_node_init(&ep->entry); // safe to free unlinked | |||
| 304 | ep->traddr = traddr; | |||
| 305 | ep->trsvcid = trsvcid; | |||
| 306 | list_add(&svc->endpoints, &ep->entry)list_add_(&svc->endpoints, &ep->entry, "../discoverd/src/mdns.c" ":" "306"); | |||
| 307 | ||||
| 308 | if (br->mctx->callbacks.service_add) | |||
| 309 | br->mctx->callbacks.service_add(ep->traddr, ep->trsvcid, | |||
| 310 | svc->transport, svc->nqn, | |||
| 311 | br->ifname, br->ifindex, | |||
| 312 | br->mctx->user_data); | |||
| 313 | } | |||
| 314 | ||||
| 315 | static void tcp_check_start(struct tcp_check *tc); | |||
| 316 | ||||
| 317 | static int tcp_check_retry_cback(sd_event_source *s __attribute__((unused)), | |||
| 318 | uint64_t usec __attribute__((unused)), | |||
| 319 | void *userdata) | |||
| 320 | { | |||
| 321 | struct tcp_check *tc = userdata; | |||
| 322 | ||||
| 323 | tc->retry_timer = sd_event_source_unref(tc->retry_timer); | |||
| 324 | tcp_check_start(tc); | |||
| 325 | ||||
| 326 | return 0; | |||
| 327 | } | |||
| 328 | ||||
| 329 | /* Close the current attempt, if any, and arm the next retry. */ | |||
| 330 | static void tcp_check_schedule_retry(struct tcp_check *tc) | |||
| 331 | { | |||
| 332 | size_t last = ARRAY_SIZE(tcp_check_retry_sec)(sizeof(tcp_check_retry_sec) / sizeof((tcp_check_retry_sec)[0 ]) + 0) - 1; | |||
| 333 | unsigned int sec; | |||
| 334 | uint64_t now; | |||
| 335 | int r; | |||
| 336 | ||||
| 337 | tc->io_source = sd_event_source_unref(tc->io_source); | |||
| 338 | if (tc->fd >= 0) { | |||
| 339 | close(tc->fd); | |||
| 340 | tc->fd = -1; | |||
| 341 | } | |||
| 342 | ||||
| 343 | sec = tcp_check_retry_sec[tc->retry_idx]; | |||
| 344 | if (tc->retry_idx < last) | |||
| 345 | tc->retry_idx++; | |||
| 346 | else | |||
| 347 | log_info_once(&tc->ceiling_logged,dmn_log_msg_once(&tc->ceiling_logged, DMN_LOG_INFO, "mdns: %s: %s:%s still unreachable, retrying every %us" , tc->br->ifname, tc->traddr, tc->trsvcid, sec) | |||
| 348 | "mdns: %s: %s:%s still unreachable, retrying every %us",dmn_log_msg_once(&tc->ceiling_logged, DMN_LOG_INFO, "mdns: %s: %s:%s still unreachable, retrying every %us" , tc->br->ifname, tc->traddr, tc->trsvcid, sec) | |||
| 349 | tc->br->ifname, tc->traddr, tc->trsvcid, sec)dmn_log_msg_once(&tc->ceiling_logged, DMN_LOG_INFO, "mdns: %s: %s:%s still unreachable, retrying every %us" , tc->br->ifname, tc->traddr, tc->trsvcid, sec); | |||
| 350 | ||||
| 351 | r = sd_event_now(tc->br->mctx->event, CLOCK_BOOTTIME7, &now); | |||
| 352 | if (r >= 0) | |||
| 353 | r = sd_event_add_time(tc->br->mctx->event, &tc->retry_timer, | |||
| 354 | CLOCK_BOOTTIME7, | |||
| 355 | now + sec * UINT64_C(1000000)1000000UL, 0, | |||
| 356 | tcp_check_retry_cback, tc); | |||
| 357 | if (r < 0) | |||
| 358 | log_err("mdns: %s: %s:%s: cannot arm retry timer: %s",dmn_log_msg(DMN_LOG_ERR, "mdns: %s: %s:%s: cannot arm retry timer: %s" , tc->br->ifname, tc->traddr, tc->trsvcid, strerror (-r)) | |||
| 359 | tc->br->ifname, tc->traddr, tc->trsvcid,dmn_log_msg(DMN_LOG_ERR, "mdns: %s: %s:%s: cannot arm retry timer: %s" , tc->br->ifname, tc->traddr, tc->trsvcid, strerror (-r)) | |||
| 360 | strerror(-r))dmn_log_msg(DMN_LOG_ERR, "mdns: %s: %s:%s: cannot arm retry timer: %s" , tc->br->ifname, tc->traddr, tc->trsvcid, strerror (-r)); | |||
| 361 | } | |||
| 362 | ||||
| 363 | /* Reachable: report the endpoint and free @tc. */ | |||
| 364 | static void tcp_check_succeeded(struct tcp_check *tc) | |||
| 365 | { | |||
| 366 | char *traddr = tc->traddr; | |||
| 367 | char *trsvcid = tc->trsvcid; | |||
| 368 | ||||
| 369 | tc->traddr = NULL((void*)0); | |||
| 370 | tc->trsvcid = NULL((void*)0); | |||
| 371 | report_endpoint(tc->br, tc->svc, traddr, trsvcid); | |||
| 372 | tcp_check_free(tc); | |||
| 373 | } | |||
| 374 | ||||
| 375 | static int tcp_check_io_cback(sd_event_source *s __attribute__((unused)), | |||
| 376 | int fd, | |||
| 377 | uint32_t revents __attribute__((unused)), | |||
| 378 | void *userdata) | |||
| 379 | { | |||
| 380 | struct tcp_check *tc = userdata; | |||
| 381 | socklen_t len = sizeof(int); | |||
| 382 | int err = 0; | |||
| 383 | ||||
| 384 | if (getsockopt(fd, SOL_SOCKET1, SO_ERROR4, &err, &len) < 0 || err) { | |||
| 385 | tcp_check_schedule_retry(tc); | |||
| 386 | return 0; | |||
| 387 | } | |||
| 388 | ||||
| 389 | tcp_check_succeeded(tc); | |||
| 390 | ||||
| 391 | return 0; | |||
| 392 | } | |||
| 393 | ||||
| 394 | static void tcp_check_start(struct tcp_check *tc) | |||
| 395 | { | |||
| 396 | int r; | |||
| 397 | ||||
| 398 | tc->fd = socket(tc->addr.ss_family, | |||
| 399 | SOCK_STREAMSOCK_STREAM | SOCK_NONBLOCKSOCK_NONBLOCK | SOCK_CLOEXECSOCK_CLOEXEC, | |||
| 400 | IPPROTO_TCPIPPROTO_TCP); | |||
| 401 | if (tc->fd < 0) { | |||
| 402 | tcp_check_schedule_retry(tc); | |||
| 403 | return; | |||
| 404 | } | |||
| 405 | ||||
| 406 | r = connect(tc->fd, (struct sockaddr *)&tc->addr, tc->addrlen); | |||
| 407 | if (r < 0 && errno(*__errno_location ()) != EINPROGRESS115) { | |||
| 408 | tcp_check_schedule_retry(tc); | |||
| 409 | return; | |||
| 410 | } | |||
| 411 | ||||
| 412 | r = sd_event_add_io(tc->br->mctx->event, &tc->io_source, tc->fd, | |||
| 413 | EPOLLOUTEPOLLOUT, tcp_check_io_cback, tc); | |||
| 414 | if (r < 0) | |||
| 415 | tcp_check_schedule_retry(tc); | |||
| 416 | } | |||
| 417 | ||||
| 418 | /* | |||
| 419 | * Start a reachability check for a tcp endpoint. Takes ownership of | |||
| 420 | * @traddr and @trsvcid. | |||
| 421 | */ | |||
| 422 | static void tcp_check_new(struct mdns_browse *br, struct mdns_service *svc, | |||
| 423 | char *traddr, char *trsvcid, | |||
| 424 | const struct sockaddr_storage *ss, socklen_t sslen) | |||
| 425 | { | |||
| 426 | struct tcp_check *tc; | |||
| 427 | ||||
| 428 | if (!traddr || !trsvcid) | |||
| 429 | goto err; | |||
| 430 | ||||
| 431 | tc = calloc(1, sizeof(*tc)); | |||
| 432 | if (!tc) | |||
| 433 | goto err; | |||
| 434 | tc->br = br; | |||
| 435 | tc->svc = svc; | |||
| 436 | tc->traddr = traddr; | |||
| 437 | tc->trsvcid = trsvcid; | |||
| 438 | tc->addr = *ss; | |||
| 439 | tc->addrlen = sslen; | |||
| 440 | tc->fd = -1; | |||
| 441 | list_add(&svc->checks, &tc->entry)list_add_(&svc->checks, &tc->entry, "../discoverd/src/mdns.c" ":" "441"); | |||
| 442 | ||||
| 443 | tcp_check_start(tc); | |||
| 444 | return; | |||
| 445 | err: | |||
| 446 | free(traddr); | |||
| 447 | free(trsvcid); | |||
| 448 | } | |||
| 449 | ||||
| 450 | /* Build the sockaddr for a tcp endpoint. Returns its length. */ | |||
| 451 | static socklen_t endpoint_sockaddr(int family, const void *addr, int port, | |||
| 452 | int ifindex, struct sockaddr_storage *ss) | |||
| 453 | { | |||
| 454 | struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ss; | |||
| 455 | struct sockaddr_in *sin = (struct sockaddr_in *)ss; | |||
| 456 | ||||
| 457 | memset(ss, 0, sizeof(*ss)); | |||
| 458 | ||||
| 459 | if (family == AF_INET2) { | |||
| 460 | sin->sin_family = AF_INET2; | |||
| 461 | sin->sin_port = htons((uint16_t)port)__bswap_16 ((uint16_t)port); | |||
| 462 | memcpy(&sin->sin_addr, addr, sizeof(sin->sin_addr)); | |||
| 463 | ||||
| 464 | return sizeof(*sin); | |||
| 465 | } | |||
| 466 | ||||
| 467 | sin6->sin6_family = AF_INET610; | |||
| 468 | sin6->sin6_port = htons((uint16_t)port)__bswap_16 ((uint16_t)port); | |||
| 469 | memcpy(&sin6->sin6_addr, addr, sizeof(sin6->sin6_addr)); | |||
| 470 | if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)(__extension__ ({ const struct in6_addr *__a = (const struct in6_addr *) (&sin6->sin6_addr); (__a->__in6_u.__u6_addr32[0 ] & __bswap_32 (0xffc00000)) == __bswap_32 (0xfe800000); } ))) | |||
| 471 | sin6->sin6_scope_id = ifindex; | |||
| 472 | ||||
| 473 | return sizeof(*sin6); | |||
| 474 | } | |||
| 475 | ||||
| 476 | /* Parse one ResolveService "services[]" entry's port and addresses. */ | |||
| 477 | static void add_endpoints(struct mdns_browse *br, struct mdns_service *svc, | |||
| 478 | sd_json_variant *service_item) | |||
| 479 | { | |||
| 480 | sd_json_variant *port_v, *addrs, *addr_item, *family_v, *bytes_v, *b; | |||
| 481 | char *trsvcid; | |||
| 482 | size_t i, n; | |||
| 483 | int port; | |||
| 484 | ||||
| 485 | port_v = sd_json_variant_by_key(service_item, "port"); | |||
| 486 | if (!port_v) | |||
| 487 | return; | |||
| 488 | ||||
| 489 | addrs = json_array(service_item, "addresses"); | |||
| 490 | if (!addrs) | |||
| 491 | return; | |||
| 492 | ||||
| 493 | port = (int)sd_json_variant_integer(port_v); | |||
| 494 | if (port < 1 || port > 65535) { | |||
| 495 | log_warn("mdns: %s: '%s': bad port %d, ignoring",dmn_log_msg(DMN_LOG_WARN, "mdns: %s: '%s': bad port %d, ignoring" , br->ifname, svc->name, port) | |||
| 496 | br->ifname, svc->name, port)dmn_log_msg(DMN_LOG_WARN, "mdns: %s: '%s': bad port %d, ignoring" , br->ifname, svc->name, port); | |||
| 497 | return; | |||
| 498 | } | |||
| 499 | ||||
| 500 | if (asprintf(&trsvcid, "%d", port) < 0) | |||
| 501 | return; | |||
| 502 | ||||
| 503 | n = sd_json_variant_elements(addrs); | |||
| 504 | for (i = 0; i < n; i++) { | |||
| 505 | union { | |||
| 506 | struct in_addr in4; | |||
| 507 | struct in6_addr in6; | |||
| 508 | } a; | |||
| 509 | char buf[INET6_ADDRSTRLEN46]; | |||
| 510 | unsigned char *p; | |||
| 511 | int family; | |||
| 512 | size_t j, nbytes; | |||
| 513 | ||||
| 514 | addr_item = sd_json_variant_by_index(addrs, i); | |||
| 515 | family_v = sd_json_variant_by_key(addr_item, "family"); | |||
| 516 | if (!family_v) | |||
| 517 | continue; | |||
| 518 | ||||
| 519 | bytes_v = json_array(addr_item, "address"); | |||
| 520 | if (!bytes_v) | |||
| 521 | continue; | |||
| 522 | ||||
| 523 | family = (int)sd_json_variant_integer(family_v); | |||
| 524 | nbytes = sd_json_variant_elements(bytes_v); | |||
| 525 | if (family == AF_INET2 && nbytes == sizeof(a.in4)) | |||
| 526 | p = (unsigned char *)&a.in4; | |||
| 527 | else if (family == AF_INET610 && nbytes == sizeof(a.in6)) | |||
| 528 | p = (unsigned char *)&a.in6; | |||
| 529 | else | |||
| 530 | continue; | |||
| 531 | ||||
| 532 | for (j = 0; j < nbytes; j++) { | |||
| 533 | b = sd_json_variant_by_index(bytes_v, j); | |||
| 534 | p[j] = (unsigned char)sd_json_variant_integer(b); | |||
| 535 | } | |||
| 536 | if (!inet_ntop(family, &a, buf, sizeof(buf))) | |||
| 537 | continue; | |||
| 538 | ||||
| 539 | if (streq(svc->transport, "tcp")(strcmp((svc->transport),("tcp")) == 0)) { | |||
| 540 | struct sockaddr_storage ss; | |||
| 541 | socklen_t sslen; | |||
| 542 | ||||
| 543 | sslen = endpoint_sockaddr(family, &a, port, br->ifindex, | |||
| 544 | &ss); | |||
| 545 | tcp_check_new(br, svc, strdup(buf), strdup(trsvcid), | |||
| 546 | &ss, sslen); | |||
| 547 | } else { | |||
| 548 | report_endpoint(br, svc, strdup(buf), strdup(trsvcid)); | |||
| 549 | } | |||
| 550 | } | |||
| 551 | ||||
| 552 | free(trsvcid); | |||
| 553 | } | |||
| 554 | ||||
| 555 | static void resolve_req_free(struct resolve_req *req) | |||
| 556 | { | |||
| 557 | if (!req) | |||
| 558 | return; | |||
| 559 | list_del_init(&req->entry)list_del_init_(&req->entry, "../discoverd/src/mdns.c" ":" "559"); | |||
| 560 | sd_varlink_flush_close_unref(req->link); | |||
| 561 | free(req->name); | |||
| 562 | free(req); | |||
| 563 | } | |||
| 564 | ||||
| 565 | static int resolve_reply_cb(sd_varlink *link __attribute__((unused)), | |||
| 566 | sd_json_variant *parameters, const char *error_id, | |||
| 567 | sd_varlink_reply_flags_t flags | |||
| 568 | __attribute__((unused)), | |||
| 569 | void *userdata) | |||
| 570 | { | |||
| 571 | struct resolve_req *req = userdata; | |||
| 572 | struct mdns_browse *br = req->br; | |||
| 573 | sd_json_variant *services; | |||
| 574 | struct mdns_service *svc; | |||
| 575 | const char *transport, *nqn; | |||
| 576 | size_t i, n; | |||
| 577 | ||||
| 578 | if (error_id) { | |||
| 579 | log_warn("mdns: %s: resolve '%s': %s", br->ifname, req->name,dmn_log_msg(DMN_LOG_WARN, "mdns: %s: resolve '%s': %s", br-> ifname, req->name, error_id) | |||
| 580 | error_id)dmn_log_msg(DMN_LOG_WARN, "mdns: %s: resolve '%s': %s", br-> ifname, req->name, error_id); | |||
| 581 | goto out; | |||
| 582 | } | |||
| 583 | ||||
| 584 | services = json_array(parameters, "services"); | |||
| 585 | if (!services) | |||
| 586 | goto out; | |||
| 587 | ||||
| 588 | if (resolve_txt(parameters, &transport, &nqn) < 0) { | |||
| 589 | log_warn("mdns: %s: resolve '%s': missing/invalid transport in TXT record",dmn_log_msg(DMN_LOG_WARN, "mdns: %s: resolve '%s': missing/invalid transport in TXT record" , br->ifname, req->name) | |||
| 590 | br->ifname, req->name)dmn_log_msg(DMN_LOG_WARN, "mdns: %s: resolve '%s': missing/invalid transport in TXT record" , br->ifname, req->name); | |||
| 591 | goto out; | |||
| 592 | } | |||
| 593 | ||||
| 594 | svc = calloc(1, sizeof(*svc)); | |||
| 595 | if (!svc) | |||
| 596 | goto out; | |||
| 597 | list_node_init(&svc->entry); // safe to free even if never list_add()'d | |||
| 598 | list_head_init(&svc->endpoints); | |||
| 599 | list_head_init(&svc->checks); | |||
| 600 | svc->name = req->name; | |||
| 601 | svc->transport = transport; | |||
| 602 | svc->nqn = nqn ? strdup(nqn) : NULL((void*)0); | |||
| 603 | req->name = NULL((void*)0); | |||
| 604 | ||||
| 605 | n = sd_json_variant_elements(services); | |||
| 606 | for (i = 0; i < n; i++) | |||
| 607 | add_endpoints(br, svc, sd_json_variant_by_index(services, i)); | |||
| 608 | ||||
| 609 | if (list_empty(&svc->endpoints)list_empty_(&svc->endpoints, "../discoverd/src/mdns.c" ":" "609") && list_empty(&svc->checks)list_empty_(&svc->checks, "../discoverd/src/mdns.c" ":" "609")) | |||
| 610 | service_free(br, svc); // nothing resolved | |||
| 611 | else | |||
| 612 | list_add(&br->services, &svc->entry)list_add_(&br->services, &svc->entry, "../discoverd/src/mdns.c" ":" "612"); | |||
| 613 | ||||
| 614 | out: | |||
| 615 | resolve_req_free(req); | |||
| 616 | return 0; | |||
| 617 | } | |||
| 618 | ||||
| 619 | static void resolve_service(struct mdns_browse *br, const char *name, | |||
| 620 | const char *type, const char *domain) | |||
| 621 | { | |||
| 622 | struct resolve_req *req; | |||
| 623 | int r; | |||
| 624 | ||||
| 625 | req = calloc(1, sizeof(*req)); | |||
| 626 | if (!req) | |||
| 627 | return; | |||
| 628 | req->br = br; | |||
| 629 | req->name = strdup(name); | |||
| 630 | if (!req->name) | |||
| 631 | goto err_req; | |||
| 632 | ||||
| 633 | r = sd_varlink_connect_address(&req->link, RESOLVE_VARLINK_ADDRESS"/run/systemd/resolve/io.systemd.Resolve"); | |||
| 634 | if (r < 0) { | |||
| 635 | log_warn("mdns: %s: resolve '%s': connect: %s", br->ifname,dmn_log_msg(DMN_LOG_WARN, "mdns: %s: resolve '%s': connect: %s" , br->ifname, name, strerror(-r)) | |||
| 636 | name, strerror(-r))dmn_log_msg(DMN_LOG_WARN, "mdns: %s: resolve '%s': connect: %s" , br->ifname, name, strerror(-r)); | |||
| 637 | goto err_name; | |||
| 638 | } | |||
| 639 | ||||
| 640 | r = sd_varlink_attach_event(req->link, br->mctx->event, | |||
| 641 | SD_EVENT_PRIORITY_NORMAL); | |||
| 642 | if (r < 0) | |||
| 643 | goto err_link; | |||
| 644 | ||||
| 645 | sd_varlink_set_userdata(req->link, req); | |||
| 646 | r = sd_varlink_bind_reply(req->link, resolve_reply_cb); | |||
| 647 | if (r < 0) | |||
| 648 | goto err_link; | |||
| 649 | ||||
| 650 | r = sd_varlink_invokebo(req->link, VARLINK_RESOLVE_SERVICE,sd_varlink_invokeb((req->link), ("io.systemd.Resolve.ResolveService" ), _SD_JSON_BUILD_OBJECT_BEGIN, _SD_JSON_BUILD_PAIR, (const char *) { "name" }, _SD_JSON_BUILD_STRING, (const char*) { name }, _SD_JSON_BUILD_PAIR, (const char*) { "type" }, _SD_JSON_BUILD_STRING , (const char*) { type }, _SD_JSON_BUILD_PAIR, (const char*) { "domain" }, _SD_JSON_BUILD_STRING, (const char*) { domain }, _SD_JSON_BUILD_PAIR, (const char*) { "ifindex" }, _SD_JSON_BUILD_INTEGER , (int64_t) { br->ifindex }, _SD_JSON_BUILD_OBJECT_END) | |||
| 651 | SD_JSON_BUILD_PAIR_STRING("name", name),sd_varlink_invokeb((req->link), ("io.systemd.Resolve.ResolveService" ), _SD_JSON_BUILD_OBJECT_BEGIN, _SD_JSON_BUILD_PAIR, (const char *) { "name" }, _SD_JSON_BUILD_STRING, (const char*) { name }, _SD_JSON_BUILD_PAIR, (const char*) { "type" }, _SD_JSON_BUILD_STRING , (const char*) { type }, _SD_JSON_BUILD_PAIR, (const char*) { "domain" }, _SD_JSON_BUILD_STRING, (const char*) { domain }, _SD_JSON_BUILD_PAIR, (const char*) { "ifindex" }, _SD_JSON_BUILD_INTEGER , (int64_t) { br->ifindex }, _SD_JSON_BUILD_OBJECT_END) | |||
| 652 | SD_JSON_BUILD_PAIR_STRING("type", type),sd_varlink_invokeb((req->link), ("io.systemd.Resolve.ResolveService" ), _SD_JSON_BUILD_OBJECT_BEGIN, _SD_JSON_BUILD_PAIR, (const char *) { "name" }, _SD_JSON_BUILD_STRING, (const char*) { name }, _SD_JSON_BUILD_PAIR, (const char*) { "type" }, _SD_JSON_BUILD_STRING , (const char*) { type }, _SD_JSON_BUILD_PAIR, (const char*) { "domain" }, _SD_JSON_BUILD_STRING, (const char*) { domain }, _SD_JSON_BUILD_PAIR, (const char*) { "ifindex" }, _SD_JSON_BUILD_INTEGER , (int64_t) { br->ifindex }, _SD_JSON_BUILD_OBJECT_END) | |||
| 653 | SD_JSON_BUILD_PAIR_STRING("domain", domain),sd_varlink_invokeb((req->link), ("io.systemd.Resolve.ResolveService" ), _SD_JSON_BUILD_OBJECT_BEGIN, _SD_JSON_BUILD_PAIR, (const char *) { "name" }, _SD_JSON_BUILD_STRING, (const char*) { name }, _SD_JSON_BUILD_PAIR, (const char*) { "type" }, _SD_JSON_BUILD_STRING , (const char*) { type }, _SD_JSON_BUILD_PAIR, (const char*) { "domain" }, _SD_JSON_BUILD_STRING, (const char*) { domain }, _SD_JSON_BUILD_PAIR, (const char*) { "ifindex" }, _SD_JSON_BUILD_INTEGER , (int64_t) { br->ifindex }, _SD_JSON_BUILD_OBJECT_END) | |||
| 654 | SD_JSON_BUILD_PAIR_INTEGER("ifindex",sd_varlink_invokeb((req->link), ("io.systemd.Resolve.ResolveService" ), _SD_JSON_BUILD_OBJECT_BEGIN, _SD_JSON_BUILD_PAIR, (const char *) { "name" }, _SD_JSON_BUILD_STRING, (const char*) { name }, _SD_JSON_BUILD_PAIR, (const char*) { "type" }, _SD_JSON_BUILD_STRING , (const char*) { type }, _SD_JSON_BUILD_PAIR, (const char*) { "domain" }, _SD_JSON_BUILD_STRING, (const char*) { domain }, _SD_JSON_BUILD_PAIR, (const char*) { "ifindex" }, _SD_JSON_BUILD_INTEGER , (int64_t) { br->ifindex }, _SD_JSON_BUILD_OBJECT_END) | |||
| 655 | br->ifindex))sd_varlink_invokeb((req->link), ("io.systemd.Resolve.ResolveService" ), _SD_JSON_BUILD_OBJECT_BEGIN, _SD_JSON_BUILD_PAIR, (const char *) { "name" }, _SD_JSON_BUILD_STRING, (const char*) { name }, _SD_JSON_BUILD_PAIR, (const char*) { "type" }, _SD_JSON_BUILD_STRING , (const char*) { type }, _SD_JSON_BUILD_PAIR, (const char*) { "domain" }, _SD_JSON_BUILD_STRING, (const char*) { domain }, _SD_JSON_BUILD_PAIR, (const char*) { "ifindex" }, _SD_JSON_BUILD_INTEGER , (int64_t) { br->ifindex }, _SD_JSON_BUILD_OBJECT_END); | |||
| 656 | if (r < 0) | |||
| 657 | goto err_link; | |||
| 658 | ||||
| 659 | list_add(&br->pending, &req->entry)list_add_(&br->pending, &req->entry, "../discoverd/src/mdns.c" ":" "659"); | |||
| 660 | return; | |||
| 661 | ||||
| 662 | err_link: | |||
| 663 | sd_varlink_flush_close_unref(req->link); | |||
| 664 | err_name: | |||
| 665 | free(req->name); | |||
| 666 | err_req: | |||
| 667 | free(req); | |||
| 668 | } | |||
| 669 | ||||
| 670 | /* Is a ResolveService for @name already in flight on @br? */ | |||
| 671 | static bool_Bool resolve_pending(struct mdns_browse *br, const char *name) | |||
| 672 | { | |||
| 673 | struct resolve_req *req; | |||
| 674 | ||||
| 675 | list_for_each(&br->pending, req, entry)for ((req) = list_node_to_off_(((void)"../discoverd/src/mdns.c" ":" "675", (&br->pending))->n.next, (((__builtin_offsetof (typeof(*req), entry) + ((typeof(req->entry) *)0 != (struct list_node *)0))))); list_node_from_off_((void *)(req), (((__builtin_offsetof (typeof(*req), entry) + ((typeof(req->entry) *)0 != (struct list_node *)0))))) != &((&br->pending))->n; (req ) = list_node_to_off_(list_node_from_off_((void *)(req), (((__builtin_offsetof (typeof(*req), entry) + ((typeof(req->entry) *)0 != (struct list_node *)0)))))->next, (((__builtin_offsetof(typeof(*req ), entry) + ((typeof(req->entry) *)0 != (struct list_node * )0)))))) { | |||
| 676 | if (req->name && streq(req->name, name)(strcmp((req->name),(name)) == 0)) | |||
| 677 | return true1; | |||
| 678 | } | |||
| 679 | return false0; | |||
| 680 | } | |||
| 681 | ||||
| 682 | static void service_added(struct mdns_browse *br, const char *name, | |||
| 683 | const char *type, const char *domain) | |||
| 684 | { | |||
| 685 | /* | |||
| 686 | * A service joins br->services only when its resolve replies. Check | |||
| 687 | * the pending resolves too, or a repeated announcement starts a | |||
| 688 | * second resolve for the same name. | |||
| 689 | */ | |||
| 690 | if (service_find(br, name) || resolve_pending(br, name)) | |||
| 691 | return; | |||
| 692 | ||||
| 693 | resolve_service(br, name, type, domain); | |||
| 694 | } | |||
| 695 | ||||
| 696 | static void service_removed(struct mdns_browse *br, const char *name) | |||
| 697 | { | |||
| 698 | struct mdns_service *svc = service_find(br, name); | |||
| 699 | ||||
| 700 | if (svc) | |||
| 701 | service_free(br, svc); | |||
| 702 | } | |||
| 703 | ||||
| 704 | static void handle_service_data(struct mdns_browse *br, sd_json_variant *item) | |||
| 705 | { | |||
| 706 | const char *update = json_str(item, "updateFlag"); | |||
| 707 | const char *name = json_str(item, "name"); | |||
| 708 | const char *type = json_str(item, "type"); | |||
| 709 | const char *domain = json_str(item, "domain"); | |||
| 710 | ||||
| 711 | if (!update || !name) | |||
| 712 | return; | |||
| 713 | ||||
| 714 | if (streq(update, "added")(strcmp((update),("added")) == 0) && type && domain) | |||
| 715 | service_added(br, name, type, domain); | |||
| 716 | else if (streq(update, "removed")(strcmp((update),("removed")) == 0)) | |||
| 717 | service_removed(br, name); | |||
| 718 | } | |||
| 719 | ||||
| 720 | static void browse_schedule_restart(struct mdns_browse *br); | |||
| 721 | ||||
| 722 | /* | |||
| 723 | * Close the browse call. Every service found through it is reported as | |||
| 724 | * removed. It is found again when the browse restarts. | |||
| 725 | */ | |||
| 726 | static void browse_disconnect(struct mdns_browse *br) | |||
| 727 | { | |||
| 728 | struct mdns_service *svc, *next_svc; | |||
| 729 | struct resolve_req *req, *next_req; | |||
| 730 | ||||
| 731 | list_for_each_safe(&br->pending, req, next_req, entry)for ((req) = list_node_to_off_(((void)"../discoverd/src/mdns.c" ":" "731", (&br->pending))->n.next, (((__builtin_offsetof (typeof(*req), entry) + ((typeof(req->entry) *)0 != (struct list_node *)0))))), (next_req) = list_node_to_off_(list_node_from_off_ ((req), (((__builtin_offsetof(typeof(*req), entry) + ((typeof (req->entry) *)0 != (struct list_node *)0)))))->next, ( ((__builtin_offsetof(typeof(*req), entry) + ((typeof(req-> entry) *)0 != (struct list_node *)0))))); list_node_from_off_ ((req), (((__builtin_offsetof(typeof(*req), entry) + ((typeof (req->entry) *)0 != (struct list_node *)0))))) != &((& br->pending))->n; (req) = (next_req), (next_req) = list_node_to_off_ (list_node_from_off_((req), (((__builtin_offsetof(typeof(*req ), entry) + ((typeof(req->entry) *)0 != (struct list_node * )0)))))->next, (((__builtin_offsetof(typeof(*req), entry) + ((typeof(req->entry) *)0 != (struct list_node *)0)))))) | |||
| 732 | resolve_req_free(req); | |||
| 733 | ||||
| 734 | list_for_each_safe(&br->services, svc, next_svc, entry)for ((svc) = list_node_to_off_(((void)"../discoverd/src/mdns.c" ":" "734", (&br->services))->n.next, (((__builtin_offsetof (typeof(*svc), entry) + ((typeof(svc->entry) *)0 != (struct list_node *)0))))), (next_svc) = list_node_to_off_(list_node_from_off_ ((svc), (((__builtin_offsetof(typeof(*svc), entry) + ((typeof (svc->entry) *)0 != (struct list_node *)0)))))->next, ( ((__builtin_offsetof(typeof(*svc), entry) + ((typeof(svc-> entry) *)0 != (struct list_node *)0))))); list_node_from_off_ ((svc), (((__builtin_offsetof(typeof(*svc), entry) + ((typeof (svc->entry) *)0 != (struct list_node *)0))))) != &((& br->services))->n; (svc) = (next_svc), (next_svc) = list_node_to_off_ (list_node_from_off_((svc), (((__builtin_offsetof(typeof(*svc ), entry) + ((typeof(svc->entry) *)0 != (struct list_node * )0)))))->next, (((__builtin_offsetof(typeof(*svc), entry) + ((typeof(svc->entry) *)0 != (struct list_node *)0)))))) | |||
| 735 | service_free(br, svc); | |||
| 736 | ||||
| 737 | br->link = sd_varlink_flush_close_unref(br->link); | |||
| 738 | } | |||
| 739 | ||||
| 740 | static int browse_reply_cb(sd_varlink *link __attribute__((unused)), | |||
| 741 | sd_json_variant *parameters, const char *error_id, | |||
| 742 | sd_varlink_reply_flags_t flags, void *userdata) | |||
| 743 | { | |||
| 744 | struct mdns_browse *br = userdata; | |||
| 745 | sd_json_variant *arr; | |||
| 746 | size_t i, n; | |||
| 747 | ||||
| 748 | if (error_id) { | |||
| 749 | log_warn("mdns: %s: browsing %s failed: %s, restarting",dmn_log_msg(DMN_LOG_WARN, "mdns: %s: browsing %s failed: %s, restarting" , br->ifname, br->type, error_id) | |||
| 750 | br->ifname, br->type, error_id)dmn_log_msg(DMN_LOG_WARN, "mdns: %s: browsing %s failed: %s, restarting" , br->ifname, br->type, error_id); | |||
| 751 | goto restart; | |||
| 752 | } | |||
| 753 | ||||
| 754 | arr = json_array(parameters, "browserServiceData"); | |||
| 755 | if (!arr) | |||
| 756 | log_warn("mdns: %s: BrowseServices reply without browserServiceData",dmn_log_msg(DMN_LOG_WARN, "mdns: %s: BrowseServices reply without browserServiceData" , br->ifname) | |||
| 757 | br->ifname)dmn_log_msg(DMN_LOG_WARN, "mdns: %s: BrowseServices reply without browserServiceData" , br->ifname); | |||
| 758 | n = arr ? sd_json_variant_elements(arr) : 0; | |||
| 759 | for (i = 0; i < n; i++) | |||
| 760 | handle_service_data(br, sd_json_variant_by_index(arr, i)); | |||
| 761 | ||||
| 762 | if (flags & SD_VARLINK_REPLY_CONTINUES) | |||
| 763 | return 0; | |||
| 764 | ||||
| 765 | log_warn("mdns: %s: browsing %s ended, restarting", br->ifname,dmn_log_msg(DMN_LOG_WARN, "mdns: %s: browsing %s ended, restarting" , br->ifname, br->type) | |||
| 766 | br->type)dmn_log_msg(DMN_LOG_WARN, "mdns: %s: browsing %s ended, restarting" , br->ifname, br->type); | |||
| 767 | restart: | |||
| 768 | browse_disconnect(br); | |||
| 769 | browse_schedule_restart(br); | |||
| 770 | ||||
| 771 | return 0; | |||
| 772 | } | |||
| 773 | ||||
| 774 | /* Open a link to systemd-resolved and start the BrowseServices call. */ | |||
| 775 | static int browse_connect(struct mdns_browse *br) | |||
| 776 | { | |||
| 777 | int r; | |||
| 778 | ||||
| 779 | r = sd_varlink_connect_address(&br->link, RESOLVE_VARLINK_ADDRESS"/run/systemd/resolve/io.systemd.Resolve"); | |||
| 780 | if (r < 0) | |||
| 781 | return r; | |||
| 782 | ||||
| 783 | /* | |||
| 784 | * The default 45 s timeout also applies to an observe call, counted | |||
| 785 | * from when it was sent. Browsing must run until stopped. | |||
| 786 | */ | |||
| 787 | r = sd_varlink_set_relative_timeout(br->link, SHR_USEC_INFINITY(18446744073709551615UL)); | |||
| 788 | if (r < 0) | |||
| 789 | goto err; | |||
| 790 | ||||
| 791 | r = sd_varlink_attach_event(br->link, br->mctx->event, | |||
| 792 | SD_EVENT_PRIORITY_NORMAL); | |||
| 793 | if (r < 0) | |||
| 794 | goto err; | |||
| 795 | ||||
| 796 | sd_varlink_set_userdata(br->link, br); | |||
| 797 | r = sd_varlink_bind_reply(br->link, browse_reply_cb); | |||
| 798 | if (r < 0) | |||
| 799 | goto err; | |||
| 800 | ||||
| 801 | r = sd_varlink_observebo(br->link, VARLINK_BROWSE_SERVICES,sd_varlink_observeb((br->link), ("io.systemd.Resolve.BrowseServices" ), _SD_JSON_BUILD_OBJECT_BEGIN, _SD_JSON_BUILD_PAIR, (const char *) { "type" }, _SD_JSON_BUILD_STRING, (const char*) { br-> type }, _SD_JSON_BUILD_PAIR, (const char*) { "ifindex" }, _SD_JSON_BUILD_INTEGER , (int64_t) { br->ifindex }, _SD_JSON_BUILD_OBJECT_END) | |||
| 802 | SD_JSON_BUILD_PAIR_STRING("type", br->type),sd_varlink_observeb((br->link), ("io.systemd.Resolve.BrowseServices" ), _SD_JSON_BUILD_OBJECT_BEGIN, _SD_JSON_BUILD_PAIR, (const char *) { "type" }, _SD_JSON_BUILD_STRING, (const char*) { br-> type }, _SD_JSON_BUILD_PAIR, (const char*) { "ifindex" }, _SD_JSON_BUILD_INTEGER , (int64_t) { br->ifindex }, _SD_JSON_BUILD_OBJECT_END) | |||
| 803 | SD_JSON_BUILD_PAIR_INTEGER("ifindex",sd_varlink_observeb((br->link), ("io.systemd.Resolve.BrowseServices" ), _SD_JSON_BUILD_OBJECT_BEGIN, _SD_JSON_BUILD_PAIR, (const char *) { "type" }, _SD_JSON_BUILD_STRING, (const char*) { br-> type }, _SD_JSON_BUILD_PAIR, (const char*) { "ifindex" }, _SD_JSON_BUILD_INTEGER , (int64_t) { br->ifindex }, _SD_JSON_BUILD_OBJECT_END) | |||
| 804 | br->ifindex))sd_varlink_observeb((br->link), ("io.systemd.Resolve.BrowseServices" ), _SD_JSON_BUILD_OBJECT_BEGIN, _SD_JSON_BUILD_PAIR, (const char *) { "type" }, _SD_JSON_BUILD_STRING, (const char*) { br-> type }, _SD_JSON_BUILD_PAIR, (const char*) { "ifindex" }, _SD_JSON_BUILD_INTEGER , (int64_t) { br->ifindex }, _SD_JSON_BUILD_OBJECT_END); | |||
| 805 | if (r < 0) | |||
| 806 | goto err; | |||
| 807 | ||||
| 808 | return 0; | |||
| 809 | err: | |||
| 810 | br->link = sd_varlink_flush_close_unref(br->link); | |||
| 811 | ||||
| 812 | return r; | |||
| 813 | } | |||
| 814 | ||||
| 815 | static int browse_restart_cback(sd_event_source *s __attribute__((unused)), | |||
| 816 | uint64_t usec __attribute__((unused)), | |||
| 817 | void *userdata) | |||
| 818 | { | |||
| 819 | struct mdns_browse *br = userdata; | |||
| 820 | int r; | |||
| 821 | ||||
| 822 | br->restart_timer = sd_event_source_unref(br->restart_timer); | |||
| 823 | ||||
| 824 | r = browse_connect(br); | |||
| 825 | if (r < 0) { | |||
| 826 | log_dbg("mdns: %s: browsing %s: %s", br->ifname, br->type,dmn_log_msg(DMN_LOG_DEBUG, "%s() - " "mdns: %s: browsing %s: %s" , __func__, br->ifname, br->type, strerror(-r)) | |||
| 827 | strerror(-r))dmn_log_msg(DMN_LOG_DEBUG, "%s() - " "mdns: %s: browsing %s: %s" , __func__, br->ifname, br->type, strerror(-r)); | |||
| 828 | browse_schedule_restart(br); | |||
| 829 | return 0; | |||
| 830 | } | |||
| 831 | ||||
| 832 | br->restart_sec = BROWSE_RESTART_MIN_SEC1; | |||
| 833 | log_info("mdns: browsing %s for %s again", br->ifname, br->type)dmn_log_msg(DMN_LOG_INFO, "mdns: browsing %s for %s again", br ->ifname, br->type); | |||
| 834 | ||||
| 835 | return 0; | |||
| 836 | } | |||
| 837 | ||||
| 838 | static void browse_schedule_restart(struct mdns_browse *br) | |||
| 839 | { | |||
| 840 | uint64_t now; | |||
| 841 | int r; | |||
| 842 | ||||
| 843 | r = sd_event_now(br->mctx->event, CLOCK_BOOTTIME7, &now); | |||
| 844 | if (r >= 0) | |||
| 845 | r = sd_event_add_time(br->mctx->event, &br->restart_timer, | |||
| 846 | CLOCK_BOOTTIME7, | |||
| 847 | now + br->restart_sec * UINT64_C(1000000)1000000UL, | |||
| 848 | 0, browse_restart_cback, br); | |||
| 849 | if (r < 0) { | |||
| 850 | log_err("mdns: %s: browsing %s: cannot arm restart timer: %s",dmn_log_msg(DMN_LOG_ERR, "mdns: %s: browsing %s: cannot arm restart timer: %s" , br->ifname, br->type, strerror(-r)) | |||
| 851 | br->ifname, br->type, strerror(-r))dmn_log_msg(DMN_LOG_ERR, "mdns: %s: browsing %s: cannot arm restart timer: %s" , br->ifname, br->type, strerror(-r)); | |||
| 852 | return; | |||
| 853 | } | |||
| 854 | ||||
| 855 | br->restart_sec *= 2; | |||
| 856 | if (br->restart_sec > BROWSE_RESTART_MAX_SEC60) | |||
| 857 | br->restart_sec = BROWSE_RESTART_MAX_SEC60; | |||
| 858 | } | |||
| 859 | ||||
| 860 | static struct mdns_browse *browse_find(struct mdns_ctx *mctx, int ifindex, | |||
| 861 | const char *type) | |||
| 862 | { | |||
| 863 | struct mdns_browse *br; | |||
| 864 | ||||
| 865 | list_for_each(&mctx->browses, br, entry)for ((br) = list_node_to_off_(((void)"../discoverd/src/mdns.c" ":" "865", (&mctx->browses))->n.next, (((__builtin_offsetof (typeof(*br), entry) + ((typeof(br->entry) *)0 != (struct list_node *)0))))); list_node_from_off_((void *)(br), (((__builtin_offsetof (typeof(*br), entry) + ((typeof(br->entry) *)0 != (struct list_node *)0))))) != &((&mctx->browses))->n; (br) = list_node_to_off_ (list_node_from_off_((void *)(br), (((__builtin_offsetof(typeof (*br), entry) + ((typeof(br->entry) *)0 != (struct list_node *)0)))))->next, (((__builtin_offsetof(typeof(*br), entry) + ((typeof(br->entry) *)0 != (struct list_node *)0)))))) { | |||
| ||||
| 866 | if (br->ifindex == ifindex && streq(br->type, type)(strcmp((br->type),(type)) == 0)) | |||
| 867 | return br; | |||
| 868 | } | |||
| 869 | return NULL((void*)0); | |||
| 870 | } | |||
| 871 | ||||
| 872 | static void browse_free(struct mdns_browse *br) | |||
| 873 | { | |||
| 874 | if (!br
| |||
| 875 | return; | |||
| 876 | ||||
| 877 | browse_disconnect(br); | |||
| 878 | sd_event_source_unref(br->restart_timer); | |||
| 879 | list_del_init(&br->entry)list_del_init_(&br->entry, "../discoverd/src/mdns.c" ":" "879"); | |||
| 880 | free(br->ifname); | |||
| 881 | free(br); | |||
| 882 | } | |||
| 883 | ||||
| 884 | /* | |||
| 885 | * Start browsing @ifname for @type. If systemd-resolved is not reachable, | |||
| 886 | * the browse is retried later. | |||
| 887 | */ | |||
| 888 | static void browse_start(struct mdns_ctx *mctx, int ifindex, | |||
| 889 | const char *ifname, const char *type) | |||
| 890 | { | |||
| 891 | struct mdns_browse *br; | |||
| 892 | int r; | |||
| 893 | ||||
| 894 | br = calloc(1, sizeof(*br)); | |||
| 895 | if (!br) | |||
| 896 | return; | |||
| 897 | list_node_init(&br->entry); // safe to free even if never list_add()'d | |||
| 898 | list_head_init(&br->services); | |||
| 899 | list_head_init(&br->pending); | |||
| 900 | br->mctx = mctx; | |||
| 901 | br->ifindex = ifindex; | |||
| 902 | br->type = type; | |||
| 903 | br->restart_sec = BROWSE_RESTART_MIN_SEC1; | |||
| 904 | br->ifname = strdup(ifname); | |||
| 905 | if (!br->ifname) { | |||
| 906 | browse_free(br); | |||
| 907 | return; | |||
| 908 | } | |||
| 909 | list_add(&mctx->browses, &br->entry)list_add_(&mctx->browses, &br->entry, "../discoverd/src/mdns.c" ":" "909"); | |||
| 910 | ||||
| 911 | r = browse_connect(br); | |||
| 912 | if (r < 0) { | |||
| 913 | log_warn("mdns: %s: browsing %s: %s, retrying", ifname, type,dmn_log_msg(DMN_LOG_WARN, "mdns: %s: browsing %s: %s, retrying" , ifname, type, strerror(-r)) | |||
| 914 | strerror(-r))dmn_log_msg(DMN_LOG_WARN, "mdns: %s: browsing %s: %s, retrying" , ifname, type, strerror(-r)); | |||
| 915 | browse_schedule_restart(br); | |||
| 916 | return; | |||
| 917 | } | |||
| 918 | ||||
| 919 | log_info("mdns: browsing %s for %s", ifname, type)dmn_log_msg(DMN_LOG_INFO, "mdns: browsing %s for %s", ifname, type); | |||
| 920 | } | |||
| 921 | ||||
| 922 | static void on_iface_add(int ifindex, const char *ifname, void *user_data) | |||
| 923 | { | |||
| 924 | struct mdns_ctx *mctx = user_data; | |||
| 925 | size_t i; | |||
| 926 | ||||
| 927 | for (i = 0; i < ARRAY_SIZE(mdns_service_types)(sizeof(mdns_service_types) / sizeof((mdns_service_types)[0]) + 0); i++) { | |||
| 928 | const char *type = mdns_service_types[i]; | |||
| 929 | ||||
| 930 | if (!browse_find(mctx, ifindex, type)) | |||
| 931 | browse_start(mctx, ifindex, ifname, type); | |||
| 932 | } | |||
| 933 | } | |||
| 934 | ||||
| 935 | static void on_iface_remove(int ifindex, | |||
| 936 | const char *ifname __attribute__((unused)), | |||
| 937 | void *user_data) | |||
| 938 | { | |||
| 939 | struct mdns_ctx *mctx = user_data; | |||
| 940 | size_t i; | |||
| 941 | ||||
| 942 | for (i = 0; i < ARRAY_SIZE(mdns_service_types)(sizeof(mdns_service_types) / sizeof((mdns_service_types)[0]) + 0); i++) { | |||
| ||||
| 943 | struct mdns_browse *br = | |||
| 944 | browse_find(mctx, ifindex, mdns_service_types[i]); | |||
| 945 | ||||
| 946 | if (br
| |||
| 947 | browse_free(br); | |||
| 948 | } | |||
| 949 | } | |||
| 950 | ||||
| 951 | int mdns_start(sd_event *event, const struct mdns_callbacks *callbacks, | |||
| 952 | void *user_data, struct mdns_ctx **mctxp) | |||
| 953 | { | |||
| 954 | static const struct netif_callbacks netif_cbs = { | |||
| 955 | .iface_add = on_iface_add, | |||
| 956 | .iface_remove = on_iface_remove, | |||
| 957 | }; | |||
| 958 | struct mdns_ctx *mctx; | |||
| 959 | sd_varlink *probe; | |||
| 960 | int r; | |||
| 961 | ||||
| 962 | r = sd_varlink_connect_address(&probe, RESOLVE_VARLINK_ADDRESS"/run/systemd/resolve/io.systemd.Resolve"); | |||
| 963 | if (r < 0) | |||
| 964 | return r; | |||
| 965 | ||||
| 966 | r = resolved_has_browse_services(probe); | |||
| 967 | sd_varlink_flush_close_unref(probe); | |||
| 968 | if (r < 0) | |||
| 969 | return r; | |||
| 970 | ||||
| 971 | mctx = calloc(1, sizeof(*mctx)); | |||
| 972 | if (!mctx) | |||
| 973 | return -ENOMEM12; | |||
| 974 | mctx->event = event; | |||
| 975 | mctx->callbacks = *callbacks; | |||
| 976 | mctx->user_data = user_data; | |||
| 977 | list_head_init(&mctx->browses); | |||
| 978 | ||||
| 979 | r = netif_start(event, &netif_cbs, mctx, &mctx->nctx); | |||
| 980 | if (r < 0) { | |||
| 981 | mdns_stop(mctx); | |||
| 982 | return r; | |||
| 983 | } | |||
| 984 | ||||
| 985 | *mctxp = mctx; | |||
| 986 | ||||
| 987 | return 0; | |||
| 988 | } | |||
| 989 | ||||
| 990 | void mdns_stop(struct mdns_ctx *mctx) | |||
| 991 | { | |||
| 992 | struct mdns_browse *br, *next; | |||
| 993 | ||||
| 994 | if (!mctx) | |||
| 995 | return; | |||
| 996 | ||||
| 997 | netif_stop(mctx->nctx); | |||
| 998 | ||||
| 999 | list_for_each_safe(&mctx->browses, br, next, entry)for ((br) = list_node_to_off_(((void)"../discoverd/src/mdns.c" ":" "999", (&mctx->browses))->n.next, (((__builtin_offsetof (typeof(*br), entry) + ((typeof(br->entry) *)0 != (struct list_node *)0))))), (next) = list_node_to_off_(list_node_from_off_((br ), (((__builtin_offsetof(typeof(*br), entry) + ((typeof(br-> entry) *)0 != (struct list_node *)0)))))->next, (((__builtin_offsetof (typeof(*br), entry) + ((typeof(br->entry) *)0 != (struct list_node *)0))))); list_node_from_off_((br), (((__builtin_offsetof(typeof (*br), entry) + ((typeof(br->entry) *)0 != (struct list_node *)0))))) != &((&mctx->browses))->n; (br) = (next ), (next) = list_node_to_off_(list_node_from_off_((br), (((__builtin_offsetof (typeof(*br), entry) + ((typeof(br->entry) *)0 != (struct list_node *)0)))))->next, (((__builtin_offsetof(typeof(*br), entry) + ((typeof(br->entry) *)0 != (struct list_node *)0)))))) | |||
| 1000 | browse_free(br); | |||
| 1001 | ||||
| 1002 | free(mctx); | |||
| 1003 | } |