2 * Copyright (c) 2019 Apple Inc. All rights reserved.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * https://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include "mdns_private.h"
19 #include "mdns_object.h"
21 #include <CoreUtils/CoreUtils.h>
22 #include <network_information.h>
25 #include <os/object_private.h>
27 //======================================================================================================================
28 // MARK: - Kind Declarations
30 #define MDNS_STRUCT(NAME) struct mdns_ ## NAME ## _s
32 // Note: The last check checks if the base's type is equal to that of the superkind. If it's not, then the pointer
33 // comparison used as the argument to sizeof will cause a "comparison of distinct pointer types" warning, so long as
34 // the warning hasn't been disabled.
36 #define MDNS_BASE_CHECK(NAME, SUPER) \
37 check_compile_time(offsetof(MDNS_STRUCT(NAME), base) == 0); \
38 check_compile_time(sizeof_field(MDNS_STRUCT(NAME), base) == sizeof(MDNS_STRUCT(SUPER))); \
39 extern int _mdns_base_type_check[sizeof(&(((mdns_ ## NAME ## _t)0)->base) == ((mdns_ ## SUPER ## _t)0))]
41 #define MDNS_OBJECT_SUBKIND_DEFINE(NAME) \
43 _mdns_ ## NAME ## _finalize(mdns_ ## NAME ## _t object); \
46 _mdns_ ## NAME ## _copy_description(mdns_ ## NAME ## _t object, bool debug, bool privacy); \
48 static const struct mdns_kind_s _mdns_ ## NAME ## _kind = { \
51 _mdns_ ## NAME ## _copy_description, \
52 _mdns_ ## NAME ## _finalize \
55 static mdns_ ## NAME ## _t \
56 _mdns_ ## NAME ## _alloc(void) \
58 mdns_ ## NAME ## _t obj = mdns_object_ ## NAME ## _alloc(sizeof(*obj)); \
59 require_quiet(obj, exit); \
61 const mdns_object_t base = (mdns_object_t)obj; \
62 base->kind = &_mdns_ ## NAME ## _kind; \
67 MDNS_BASE_CHECK(NAME, object)
69 typedef char * (*mdns_copy_description_f
)(mdns_any_t object
, bool debug
, bool privacy
);
70 typedef void (*mdns_finalize_f
)(mdns_any_t object
);
72 typedef const struct mdns_kind_s
* mdns_kind_t
;
74 mdns_kind_t superkind
; // This kind's superkind.
75 const char * name
; // Name of this kind.
76 mdns_copy_description_f copy_description
; // Creates a textual description of object.
77 mdns_finalize_f finalize
; // Releases object's resources right before the object is freed.
80 //======================================================================================================================
81 // MARK: - mdns_object Kind Definition
83 struct mdns_object_s
{
84 _OS_OBJECT_HEADER(const void *_os_obj_isa
, _os_obj_refcnt
, _os_obj_xref_cnt
);
85 mdns_kind_t kind
; // Pointer to an object's kind.
88 static const struct mdns_kind_s _mdns_object_kind
= {
89 NULL
, // No superkind.
91 NULL
, // No copy_description method.
92 NULL
// No finalize method.
96 _mdns_cf_collection_callback_retain(CFAllocatorRef allocator
, const void *object
);
99 _mdns_cf_collection_callback_release(CFAllocatorRef allocator
, const void *object
);
102 _mdns_cf_collection_callback_copy_description(const void *object
);
104 const CFArrayCallBacks mdns_cfarray_callbacks
= {
106 _mdns_cf_collection_callback_retain
, // retain
107 _mdns_cf_collection_callback_release
, // release
108 _mdns_cf_collection_callback_copy_description
, // copy description
109 NULL
// equal (NULL for pointer equality)
112 //======================================================================================================================
113 // MARK: - mdns_interface_monitor Kind Definition
115 struct mdns_interface_monitor_s
{
116 struct mdns_object_s base
; // Object base.
117 mdns_interface_monitor_t next
; // Next monitor in list.
118 dispatch_queue_t user_queue
; // User's queue for invoking handlers.
119 nw_path_evaluator_t path_evaluator
; // Path evaluator for interface properties.
120 dispatch_source_t update_source
; // Data source for triggering user's update handler.
121 mdns_interface_monitor_update_handler_t update_handler
; // User's update handler.
122 mdns_event_handler_t event_handler
; // User's event handler.
123 char * ifname
; // Name of monitored interface.
124 uint32_t ifindex
; // Index of monitored interface.
125 mdns_interface_flags_t pending_flags
; // The latest interface flags from path updates.
126 mdns_interface_flags_t flags
; // The current interface flags made known to user.
127 bool user_activated
; // True if user called activate method.
128 bool activated
; // True if the monitor has been activated.
129 bool invalidated
; // True if the monitor has been invalidated.
130 bool path_evaluator_started
; // True if the path evaluator has been started.
133 MDNS_OBJECT_SUBKIND_DEFINE(interface_monitor
);
135 //======================================================================================================================
136 // MARK: - Local Prototypes
138 static dispatch_queue_t
139 _mdns_internal_queue(void);
141 static dispatch_queue_t
142 _mdns_nwi_state_mutex_queue(void);
145 _mdns_interface_monitor_activate_async(mdns_interface_monitor_t monitor
);
148 _mdns_interface_monitor_terminate(mdns_interface_monitor_t me
, const OSStatus error
);
150 static mdns_interface_flags_t
151 _mdns_get_interface_flags_from_nw_path(nw_path_t path
, mdns_interface_flags_t current_flags
);
153 static mdns_interface_flags_t
154 _mdns_get_interface_flags_from_nwi_state(const char *ifname
, mdns_interface_flags_t current_flags
);
157 _mdns_snprintf_add(char **ptr
, const char *lim
, const char *fmt
, ...);
160 _mdns_start_nwi_state_monitoring(void);
162 #if !defined(nw_forget)
163 #define nw_forget(X) ForgetCustom(X, nw_release)
166 #if !defined(nw_release_null_safe)
167 #define nw_release_null_safe(X) do { if (X) { nw_release(X); } } while (0)
170 #if !defined(nwi_state_release_null_safe)
171 #define nwi_state_release_null_safe(X) do { if (X) { nwi_state_release(X); } } while (0)
174 //======================================================================================================================
177 static mdns_interface_monitor_t g_monitor_list
= NULL
;
178 static nwi_state_t g_nwi_state
= NULL
;
180 //======================================================================================================================
183 static dispatch_queue_t
184 _mdns_internal_queue(void)
186 static dispatch_once_t s_once
= 0;
187 static dispatch_queue_t s_queue
= NULL
;
188 dispatch_once(&s_once
,
190 s_queue
= dispatch_queue_create("com.apple.mdns.internal_queue", DISPATCH_QUEUE_SERIAL
);
195 //======================================================================================================================
197 static dispatch_queue_t
198 _mdns_nwi_state_mutex_queue(void)
200 static dispatch_once_t s_once
= 0;
201 static dispatch_queue_t s_queue
= NULL
;
202 dispatch_once(&s_once
,
204 s_queue
= dispatch_queue_create("com.apple.mdns.nwi_state_mutex", DISPATCH_QUEUE_SERIAL
);
209 //======================================================================================================================
211 #define MDNS_LOG_CATEGORY_DEFINE(SHORT_NAME, CATEGORY_STR) \
213 _mdns_ ## SHORT_NAME ## _log(void) \
215 static dispatch_once_t s_once = 0; \
216 static os_log_t s_log = NULL; \
217 dispatch_once(&s_once, \
219 s_log = os_log_create("com.apple.mdns", CATEGORY_STR); \
223 extern int _mdns_dummy_variable
225 MDNS_LOG_CATEGORY_DEFINE(ifmon
, "interface_monitor");
226 MDNS_LOG_CATEGORY_DEFINE(nwi
, "NWI");
228 //======================================================================================================================
229 // MARK: - mdns_object Public Methods
232 mdns_retain(mdns_any_t object
)
234 os_retain(object
.base
);
237 //======================================================================================================================
240 mdns_release(mdns_any_t object
)
242 os_release(object
.base
);
245 //======================================================================================================================
248 mdns_copy_description(mdns_any_t object
)
250 return mdns_object_copy_description(object
, false, false);
253 //======================================================================================================================
254 // MARK: - mdns_object Private Methods
257 mdns_object_copy_description(mdns_any_t object
, bool debug
, bool privacy
)
259 for (mdns_kind_t kind
= object
.base
->kind
; kind
; kind
= kind
->superkind
) {
260 if (kind
->copy_description
) {
261 return kind
->copy_description(object
, debug
, privacy
);
267 //======================================================================================================================
270 mdns_object_copy_description_as_cfstring(mdns_any_t object
, bool debug
, bool privacy
)
272 CFStringRef description
= NULL
;
273 char *cstring
= mdns_object_copy_description(object
, debug
, privacy
);
274 require_quiet(cstring
, exit
);
276 description
= CFStringCreateWithCStringNoCopy(NULL
, cstring
, kCFStringEncodingUTF8
, kCFAllocatorMalloc
);
277 require_quiet(description
, exit
);
281 FreeNullSafe(cstring
);
285 //======================================================================================================================
288 mdns_object_finalize(mdns_any_t object
)
290 for (mdns_kind_t kind
= object
.base
->kind
; kind
; kind
= kind
->superkind
) {
291 if (kind
->finalize
) {
292 kind
->finalize(object
);
297 //======================================================================================================================
300 _mdns_cf_collection_callback_retain(__unused CFAllocatorRef allocator
, const void *object
)
302 mdns_retain((mdns_object_t
)object
);
306 //======================================================================================================================
309 _mdns_cf_collection_callback_release(__unused CFAllocatorRef allocator
, const void *object
)
311 mdns_release((mdns_object_t
)object
);
314 //======================================================================================================================
317 _mdns_cf_collection_callback_copy_description(const void *object
)
319 return mdns_object_copy_description_as_cfstring((mdns_object_t
)object
, false, false);
322 //======================================================================================================================
323 // MARK: - mdns_interface_monitor Public Methods
325 mdns_interface_monitor_t
326 mdns_interface_monitor_create(uint32_t interface_index
)
328 mdns_interface_monitor_t monitor
= NULL
;
329 nw_interface_t interface
= NULL
;
330 nw_parameters_t params
= NULL
;
332 mdns_interface_monitor_t obj
= _mdns_interface_monitor_alloc();
333 require_quiet(obj
, exit
);
335 obj
->ifindex
= interface_index
;
336 char ifname
[IF_NAMESIZE
+ 1];
337 if (if_indextoname(obj
->ifindex
, ifname
) == NULL
) {
338 os_log_error(_mdns_ifmon_log(), "if_indextoname returned NULL for index %u", obj
->ifindex
);
341 obj
->ifname
= strdup(ifname
);
342 require_quiet(obj
->ifname
, exit
);
344 interface
= nw_interface_create_with_index(obj
->ifindex
);
346 os_log_error(_mdns_ifmon_log(), "nw_interface_create_with_index returned NULL for index %u", obj
->ifindex
);
350 params
= nw_parameters_create();
351 require_quiet(params
, exit
);
353 nw_parameters_require_interface(params
, interface
);
354 obj
->path_evaluator
= nw_path_create_evaluator_for_endpoint(NULL
, params
);
355 if (!obj
->path_evaluator
) {
356 os_log_error(_mdns_ifmon_log(), "nw_path_create_evaluator_for_endpoint returned NULL for params: %@", params
);
360 nw_path_t path
= nw_path_evaluator_copy_path(obj
->path_evaluator
);
361 require_quiet(path
, exit
);
363 obj
->pending_flags
= _mdns_get_interface_flags_from_nw_path(path
, mdns_interface_flag_null
);
364 obj
->pending_flags
= _mdns_get_interface_flags_from_nwi_state(obj
->ifname
, obj
->pending_flags
);
365 obj
->flags
= obj
->pending_flags
;
375 nw_release_null_safe(interface
);
376 nw_release_null_safe(params
);
380 //======================================================================================================================
383 mdns_interface_monitor_activate(mdns_interface_monitor_t me
)
385 if (!me
->user_activated
) {
386 if (me
->user_queue
) {
387 _mdns_interface_monitor_activate_async(me
);
389 me
->user_activated
= true;
393 //======================================================================================================================
396 mdns_interface_monitor_invalidate(mdns_interface_monitor_t me
)
399 dispatch_async(_mdns_internal_queue(),
401 if (!me
->invalidated
) {
402 _mdns_interface_monitor_terminate(me
, kNoErr
);
403 me
->invalidated
= true;
409 //======================================================================================================================
412 mdns_interface_monitor_set_queue(mdns_interface_monitor_t me
, dispatch_queue_t queue
)
414 if (!me
->user_activated
) {
415 dispatch_retain(queue
);
416 dispatch_release_null_safe(me
->user_queue
);
417 me
->user_queue
= queue
;
418 } else if (!me
->user_queue
) {
419 me
->user_queue
= queue
;
420 dispatch_retain(me
->user_queue
);
421 _mdns_interface_monitor_activate_async(me
);
425 //======================================================================================================================
428 mdns_interface_monitor_set_event_handler(mdns_interface_monitor_t me
, mdns_event_handler_t handler
)
430 mdns_event_handler_t
const new_handler
= handler
? Block_copy(handler
) : NULL
;
431 if (me
->event_handler
) {
432 Block_release(me
->event_handler
);
434 me
->event_handler
= new_handler
;
437 //======================================================================================================================
440 mdns_interface_monitor_set_update_handler(mdns_interface_monitor_t me
, mdns_interface_monitor_update_handler_t handler
)
442 mdns_interface_monitor_update_handler_t
const new_handler
= handler
? Block_copy(handler
) : NULL
;
443 if (me
->update_handler
) {
444 Block_release(me
->update_handler
);
446 me
->update_handler
= new_handler
;
449 //======================================================================================================================
452 mdns_interface_monitor_get_interface_index(mdns_interface_monitor_t me
)
457 //======================================================================================================================
460 mdns_interface_monitor_has_ipv4_connectivity(mdns_interface_monitor_t me
)
462 return ((me
->flags
& mdns_interface_flag_ipv4_connectivity
) ? true : false);
465 //======================================================================================================================
468 mdns_interface_monitor_has_ipv6_connectivity(mdns_interface_monitor_t me
)
470 return ((me
->flags
& mdns_interface_flag_ipv6_connectivity
) ? true : false);
473 //======================================================================================================================
476 mdns_interface_monitor_is_expensive(mdns_interface_monitor_t me
)
478 return ((me
->flags
& mdns_interface_flag_expensive
) ? true : false);
481 //======================================================================================================================
484 mdns_interface_monitor_is_constrained(mdns_interface_monitor_t me
)
486 return ((me
->flags
& mdns_interface_flag_constrained
) ? true : false);
489 //======================================================================================================================
492 mdns_interface_monitor_is_clat46(mdns_interface_monitor_t me
)
494 return ((me
->flags
& mdns_interface_flag_clat46
) ? true : false);
497 //======================================================================================================================
498 // MARK: - mdns_interface_monitor Private Methods
501 mdns_interface_flags_t flag
;
503 } mdns_interface_flag_description_t
;
505 const mdns_interface_flag_description_t mdns_interface_flag_descriptions
[] = {
506 { mdns_interface_flag_ipv4_connectivity
, "IPv4" },
507 { mdns_interface_flag_ipv6_connectivity
, "IPv6" },
508 { mdns_interface_flag_expensive
, "expensive" },
509 { mdns_interface_flag_constrained
, "constrained" },
510 { mdns_interface_flag_clat46
, "CLAT46" }
514 _mdns_interface_monitor_copy_description(mdns_interface_monitor_t me
, const bool debug
, __unused
const bool privacy
)
516 char * description
= NULL
;
519 const char * const lim
= &buffer
[countof(buffer
)];
524 n
= _mdns_snprintf_add(&dst
, lim
, "mdns_%s (%p): ", me
->base
.kind
->name
, me
);
525 require_quiet(n
>= 0, exit
);
527 n
= _mdns_snprintf_add(&dst
, lim
, "interface %s (%u): ", me
->ifname
, me
->ifindex
);
528 require_quiet(n
>= 0, exit
);
530 const char *separator
= "";
531 for (size_t i
= 0; i
< countof(mdns_interface_flag_descriptions
); ++i
) {
532 const mdns_interface_flag_description_t
* const flag_desc
= &mdns_interface_flag_descriptions
[i
];
533 if (me
->flags
& flag_desc
->flag
) {
534 n
= _mdns_snprintf_add(&dst
, lim
, "%s%s", separator
, flag_desc
->desc
);
535 require_quiet(n
>= 0, exit
);
539 description
= strdup(buffer
);
545 //======================================================================================================================
548 _mdns_interface_monitor_finalize(mdns_interface_monitor_t me
)
550 dispatch_forget(&me
->user_queue
);
551 nw_forget(&me
->path_evaluator
);
552 BlockForget(&me
->update_handler
);
553 BlockForget(&me
->event_handler
);
554 ForgetMem(&me
->ifname
);
557 //======================================================================================================================
560 _mdns_interface_monitor_activate_internal(mdns_interface_monitor_t monitor
);
563 _mdns_interface_monitor_activate_async(mdns_interface_monitor_t me
)
566 dispatch_async(_mdns_internal_queue(),
568 _mdns_interface_monitor_activate_internal(me
);
574 _mdns_interface_monitor_activate_internal(mdns_interface_monitor_t me
)
577 require_action_quiet(!me
->activated
&& !me
->invalidated
, exit
, err
= kNoErr
);
578 me
->activated
= true;
580 me
->update_source
= dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_REPLACE
, 0, 0, me
->user_queue
);
581 require_action_quiet(me
->update_source
, exit
, err
= kNoResourcesErr
);
584 const dispatch_source_t update_source
= me
->update_source
;
585 dispatch_source_set_event_handler(me
->update_source
,
587 const unsigned long data
= dispatch_source_get_data(update_source
);
588 const mdns_interface_flags_t new_flags
= ((mdns_interface_flags_t
)data
) & ~mdns_interface_flag_reserved
;
589 const mdns_interface_flags_t changed_flags
= me
->flags
^ new_flags
;
590 if (changed_flags
!= 0) {
591 me
->flags
= new_flags
;
592 if (me
->update_handler
) {
593 me
->update_handler(changed_flags
);
597 dispatch_source_set_cancel_handler(me
->update_source
,
601 dispatch_activate(me
->update_source
);
604 nw_path_evaluator_set_update_handler(me
->path_evaluator
, _mdns_internal_queue(),
607 const mdns_interface_flags_t new_flags
= _mdns_get_interface_flags_from_nw_path(path
, me
->pending_flags
);
608 if (new_flags
!= me
->pending_flags
) {
609 me
->pending_flags
= new_flags
;
610 if (me
->update_source
) {
611 // Note: mdns_interface_flag_reserved is used to ensure that the data is non-zero. According to the
612 // dispatch_source_create(3) man page, if the data value is zero, the source handler won't be invoked.
613 dispatch_source_merge_data(me
->update_source
, me
->pending_flags
| mdns_interface_flag_reserved
);
617 nw_path_evaluator_set_cancel_handler(me
->path_evaluator
,
621 nw_path_evaluator_start(me
->path_evaluator
);
622 me
->path_evaluator_started
= true;
624 mdns_interface_monitor_t
*p
= &g_monitor_list
;
631 // This is called after adding the monitor to the global list to ensure that the initial NWI state check is aware
632 // that the interface monitor exists.
633 _mdns_start_nwi_state_monitoring();
638 _mdns_interface_monitor_terminate(me
, err
);
642 //======================================================================================================================
645 _mdns_interface_monitor_terminate(mdns_interface_monitor_t me
, const OSStatus error
)
647 dispatch_source_forget(&me
->update_source
);
648 if (me
->path_evaluator
) {
649 if (me
->path_evaluator_started
) {
650 nw_path_evaluator_cancel(me
->path_evaluator
);
652 nw_forget(&me
->path_evaluator
);
654 for (mdns_interface_monitor_t
*p
= &g_monitor_list
; *p
; p
= &(*p
)->next
) {
663 dispatch_async(me
->user_queue
,
665 if (me
->event_handler
) {
666 me
->event_handler(error
? mdns_event_error
: mdns_event_invalidated
, error
);
672 //======================================================================================================================
673 // MARK: - NW Path Helpers
675 #define MDNS_INTERFACE_FLAGS_FROM_NWPATH \
676 (mdns_interface_flag_ipv4_connectivity | \
677 mdns_interface_flag_ipv6_connectivity | \
678 mdns_interface_flag_expensive | \
679 mdns_interface_flag_constrained)
681 static mdns_interface_flags_t
682 _mdns_get_interface_flags_from_nw_path(nw_path_t path
, mdns_interface_flags_t current_flags
)
684 mdns_interface_flags_t flags
= current_flags
& ~MDNS_INTERFACE_FLAGS_FROM_NWPATH
;
685 if (nw_path_has_ipv4(path
)) {
686 flags
|= mdns_interface_flag_ipv4_connectivity
;
688 if (nw_path_has_ipv6(path
)) {
689 flags
|= mdns_interface_flag_ipv6_connectivity
;
691 if (nw_path_is_expensive(path
)) {
692 flags
|= mdns_interface_flag_expensive
;
694 if (__builtin_available(macOS
10.15, *)) {
695 if (nw_path_is_constrained(path
)) {
696 flags
|= mdns_interface_flag_constrained
;
702 //======================================================================================================================
703 // MARK: - NWI Helpers
705 #if !defined(NWI_IFSTATE_FLAGS_HAS_CLAT46)
706 #define NWI_IFSTATE_FLAGS_HAS_CLAT46 0x0040
709 #define MDNS_INTERFACE_FLAGS_FROM_NWI_STATE mdns_interface_flag_clat46
711 static mdns_interface_flags_t
712 _mdns_get_interface_flags_from_nwi_state(const char *ifname
, mdns_interface_flags_t current_flags
)
714 __block nwi_ifstate_flags ifstate_flags
= 0;
715 dispatch_sync(_mdns_nwi_state_mutex_queue(),
718 const nwi_ifstate_t ifstate
= nwi_state_get_ifstate(g_nwi_state
, ifname
);
720 ifstate_flags
= nwi_ifstate_get_flags(ifstate
);
724 mdns_interface_flags_t flags
= current_flags
& ~MDNS_INTERFACE_FLAGS_FROM_NWI_STATE
;
725 if (ifstate_flags
& NWI_IFSTATE_FLAGS_HAS_CLAT46
) {
726 flags
|= mdns_interface_flag_clat46
;
731 //======================================================================================================================
734 _mdns_nwi_state_update(void);
737 _mdns_start_nwi_state_monitoring(void)
739 static int s_nwi_notify_token
= NOTIFY_TOKEN_INVALID
;
740 if (s_nwi_notify_token
== NOTIFY_TOKEN_INVALID
) {
741 const uint32_t status
= notify_register_dispatch(nwi_state_get_notify_key(), &s_nwi_notify_token
,
742 _mdns_internal_queue(),
743 ^(__unused
int token
)
745 _mdns_nwi_state_update();
747 if (s_nwi_notify_token
== NOTIFY_TOKEN_INVALID
) {
748 os_log_error(_mdns_nwi_log(), "Failed to register for NWI state notifications (status %u)", status
);
750 _mdns_nwi_state_update();
756 _mdns_nwi_state_update(void)
758 nwi_state_t new_state
= nwi_state_copy();
760 os_log_error(_mdns_nwi_log(), "Failed to copy NWI state");
762 __block nwi_state_t old_state
;
763 dispatch_sync(_mdns_nwi_state_mutex_queue(),
765 old_state
= g_nwi_state
;
766 g_nwi_state
= new_state
;
768 nwi_state_release_null_safe(old_state
);
769 for (mdns_interface_monitor_t m
= g_monitor_list
; m
; m
= m
->next
) {
770 const mdns_interface_flags_t new_flags
= _mdns_get_interface_flags_from_nwi_state(m
->ifname
, m
->pending_flags
);
771 if (new_flags
!= m
->pending_flags
) {
772 m
->pending_flags
= new_flags
;
773 if (m
->update_source
) {
774 // Note: mdns_interface_flag_reserved is used to ensure that the data is non-zero. According to the
775 // dispatch_source_create(3) man page, if the data value is zero, the source handler won't be invoked.
776 dispatch_source_merge_data(m
->update_source
, m
->pending_flags
| mdns_interface_flag_reserved
);
782 //======================================================================================================================
783 // MARK: - General Helpers
786 _mdns_snprintf_add(char **ptr
, const char *lim
, const char *fmt
, ...)
788 char * const dst
= *ptr
;
789 const size_t len
= (size_t)(lim
- dst
);
792 require_action_quiet(len
> 0, exit
, n
= 0);
796 n
= vsnprintf(dst
, len
, fmt
, args
);
798 require_quiet(n
>= 0, exit
);
800 if (((size_t)n
) > len
) {