diff --git a/bin/mdns-netbsd4be/mdns-advertiser b/bin/mdns-netbsd4be/mdns-advertiser index ec62a46..8987f16 100755 Binary files a/bin/mdns-netbsd4be/mdns-advertiser and b/bin/mdns-netbsd4be/mdns-advertiser differ diff --git a/bin/mdns-netbsd4le/mdns-advertiser b/bin/mdns-netbsd4le/mdns-advertiser index 59f02d0..57e0607 100755 Binary files a/bin/mdns-netbsd4le/mdns-advertiser and b/bin/mdns-netbsd4le/mdns-advertiser differ diff --git a/bin/mdns/mdns-advertiser b/bin/mdns/mdns-advertiser index 5d0e23f..6e55d1b 100755 Binary files a/bin/mdns/mdns-advertiser and b/bin/mdns/mdns-advertiser differ diff --git a/bin/nbns-netbsd4be/nbns-advertiser b/bin/nbns-netbsd4be/nbns-advertiser index e898e0a..826cf33 100755 Binary files a/bin/nbns-netbsd4be/nbns-advertiser and b/bin/nbns-netbsd4be/nbns-advertiser differ diff --git a/bin/nbns-netbsd4le/nbns-advertiser b/bin/nbns-netbsd4le/nbns-advertiser index c93bce2..7ccffe0 100755 Binary files a/bin/nbns-netbsd4le/nbns-advertiser and b/bin/nbns-netbsd4le/nbns-advertiser differ diff --git a/bin/nbns/nbns-advertiser b/bin/nbns/nbns-advertiser index d07b240..d615805 100755 Binary files a/bin/nbns/nbns-advertiser and b/bin/nbns/nbns-advertiser differ diff --git a/build/auto-ip-common.inc b/build/auto-ip-common.inc index d1ffd40..ddf8767 100644 --- a/build/auto-ip-common.inc +++ b/build/auto-ip-common.inc @@ -7,6 +7,18 @@ #define TC_AUTO_IP_UNUSED #endif +#if defined(__NetBSD__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) +#include +#include +#endif + +#ifndef MAX_LINK_IPV4_ADDRS +#define MAX_LINK_IPV4_ADDRS 8 +#endif +#ifndef MAX_LINK_IPV6_ADDRS +#define MAX_LINK_IPV6_ADDRS 8 +#endif + struct iface_context { char name[IFNAMSIZ]; uint32_t ipv4_addr; @@ -17,6 +29,45 @@ struct iface_context { struct iface_context_set { struct iface_context contexts[MAX_IFACE_CONTEXTS]; size_t count; + int truncated; +}; + +struct link_ipv4_addr { + uint32_t addr; + uint32_t netmask; +}; + +struct link_ipv6_addr { + struct in6_addr addr; + unsigned int scope_id; + int prefix_len; + int link_local; +}; + +struct link_context { + char name[IFNAMSIZ]; + int flags; + unsigned int ifindex; + int mdns_ipv4_transport; + uint32_t mdns_ipv4_transport_addr; + int mdns_ipv6_transport; + struct link_ipv4_addr ipv4[MAX_LINK_IPV4_ADDRS]; + size_t ipv4_count; + struct link_ipv6_addr ipv6[MAX_LINK_IPV6_ADDRS]; + size_t ipv6_count; +}; + +struct link_context_set { + struct link_context links[MAX_IFACE_CONTEXTS]; + size_t count; + int truncated; +}; + +struct ifconf_entry_view { + char name[IFNAMSIZ]; + const struct sockaddr *addr; + size_t addr_len; + size_t step; }; static int TC_AUTO_IP_UNUSED runtime_ipv4_is_usable(uint32_t ipv4_addr) { @@ -39,6 +90,22 @@ static int TC_AUTO_IP_UNUSED runtime_ipv4_is_usable(uint32_t ipv4_addr) { return 1; } +static int TC_AUTO_IP_UNUSED runtime_ipv4_is_bindable(uint32_t ipv4_addr) { + uint32_t host_order = ntohl(ipv4_addr); + unsigned int first_octet = (unsigned int)((host_order >> 24) & 0xff); + + if (ipv4_addr == 0 || host_order == 0xffffffffU) { + return 0; + } + if (first_octet == 0 || first_octet == 127) { + return 0; + } + if (first_octet >= 224) { + return 0; + } + return 1; +} + static int TC_AUTO_IP_UNUSED iface_flags_are_usable(int flags, int require_running) { if ((flags & IFF_UP) == 0) { return 0; @@ -62,8 +129,26 @@ static int TC_AUTO_IP_UNUSED ifreq_table_uses_fixed_entries(size_t ifc_len) { return sizeof(struct ifreq) > 64 && ifc_len > 0 && (ifc_len % sizeof(struct ifreq)) == 0; } -static size_t TC_AUTO_IP_UNUSED ifreq_entry_size(const struct ifreq *ifr, size_t remaining, int fixed_entries) { +static size_t TC_AUTO_IP_UNUSED ifreq_sockaddr_len(const struct ifreq *ifr, size_t remaining) { size_t sockaddr_len = sizeof(struct sockaddr); + + (void)ifr; + if (remaining < IFNAMSIZ + sizeof(struct sockaddr)) { + return 0; + } +#if defined(__NetBSD__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) + if (ifr->ifr_addr.sa_len > sockaddr_len) { + sockaddr_len = ifr->ifr_addr.sa_len; + } +#endif + if (sockaddr_len > remaining - IFNAMSIZ) { + return 0; + } + return sockaddr_len; +} + +static size_t TC_AUTO_IP_UNUSED ifreq_entry_size(const struct ifreq *ifr, size_t remaining, int fixed_entries) { + size_t sockaddr_len; size_t step; size_t align; @@ -74,14 +159,10 @@ static size_t TC_AUTO_IP_UNUSED ifreq_entry_size(const struct ifreq *ifr, size_t return sizeof(*ifr); } - if (remaining < IFNAMSIZ + sizeof(struct sockaddr)) { + sockaddr_len = ifreq_sockaddr_len(ifr, remaining); + if (sockaddr_len == 0) { return 0; } -#if defined(__NetBSD__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) - if (ifr->ifr_addr.sa_len > sockaddr_len) { - sockaddr_len = ifr->ifr_addr.sa_len; - } -#endif step = IFNAMSIZ + sockaddr_len; align = sizeof(long); @@ -110,6 +191,35 @@ static int TC_AUTO_IP_UNUSED copy_ifreq_entry(struct ifreq *out, const char *cur return 0; } +static int TC_AUTO_IP_UNUSED ifconf_entry_view_from_cursor(struct ifconf_entry_view *out, + const char *cursor, + size_t remaining, + int fixed_entries) { + struct ifreq ifr; + size_t addr_len; + size_t step; + + if (copy_ifreq_entry(&ifr, cursor, remaining) != 0) { + return -1; + } + step = ifreq_entry_size(&ifr, remaining, fixed_entries); + if (step == 0) { + return -1; + } + addr_len = ifreq_sockaddr_len(&ifr, remaining); + if (addr_len == 0) { + return -1; + } + + memset(out, 0, sizeof(*out)); + memcpy(out->name, ifr.ifr_name, IFNAMSIZ); + out->name[IFNAMSIZ - 1] = '\0'; + out->addr = (const struct sockaddr *)(const void *)(cursor + IFNAMSIZ); + out->addr_len = addr_len; + out->step = step; + return 0; +} + static int TC_AUTO_IP_UNUSED netmask_prefix_length(uint32_t netmask) { uint32_t mask = ntohl(netmask); int prefix = 0; @@ -183,6 +293,148 @@ static int TC_AUTO_IP_UNUSED ipv4_is_rfc1918(uint32_t ipv4_addr) { return 0; } +static int TC_AUTO_IP_UNUSED ipv4_is_link_local(uint32_t ipv4_addr) { + uint32_t host_order = ntohl(ipv4_addr); + return ((host_order >> 24) & 0xff) == 169 && ((host_order >> 16) & 0xff) == 254; +} + +static uint32_t TC_AUTO_IP_UNUSED ipv4_link_local_netmask(void) { + return htonl(0xffff0000U); +} + +static int TC_AUTO_IP_UNUSED ipv6_is_unspecified_addr(const struct in6_addr *addr) { + static const unsigned char zero[16] = {0}; + return memcmp(addr->s6_addr, zero, sizeof(zero)) == 0; +} + +static int TC_AUTO_IP_UNUSED ipv6_is_loopback_addr(const struct in6_addr *addr) { + static const unsigned char loopback[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + return memcmp(addr->s6_addr, loopback, sizeof(loopback)) == 0; +} + +static int TC_AUTO_IP_UNUSED ipv6_is_multicast_addr(const struct in6_addr *addr) { + return addr->s6_addr[0] == 0xff; +} + +static int TC_AUTO_IP_UNUSED ipv6_is_link_local_addr(const struct in6_addr *addr) { + return addr->s6_addr[0] == 0xfe && (addr->s6_addr[1] & 0xc0) == 0x80; +} + +static int TC_AUTO_IP_UNUSED runtime_ipv6_is_usable(const struct in6_addr *addr) { + return !ipv6_is_unspecified_addr(addr) && + !ipv6_is_loopback_addr(addr) && + !ipv6_is_multicast_addr(addr); +} + +static int TC_AUTO_IP_UNUSED runtime_ipv6_is_bindable(const struct in6_addr *addr) { + return runtime_ipv6_is_usable(addr) && !ipv6_is_link_local_addr(addr); +} + +static int TC_AUTO_IP_UNUSED ipv6_prefix_length_from_mask(const struct in6_addr *mask) { + int prefix = 0; + int saw_zero = 0; + size_t i; + + for (i = 0; i < sizeof(mask->s6_addr); i++) { + unsigned char byte = mask->s6_addr[i]; + int bit; + for (bit = 7; bit >= 0; bit--) { + if ((byte & (1U << bit)) != 0) { + if (saw_zero) { + return -1; + } + prefix++; + } else { + saw_zero = 1; + } + } + } + return prefix == 0 ? -1 : prefix; +} + +static int TC_AUTO_IP_UNUSED ipv6_prefix_matches(const struct in6_addr *a, + const struct in6_addr *b, + int prefix_len) { + int full_bytes; + int remaining_bits; + unsigned char mask; + + if (prefix_len < 0) { + return 0; + } + if (prefix_len == 0) { + return 1; + } + if (prefix_len > 128) { + prefix_len = 128; + } + full_bytes = prefix_len / 8; + remaining_bits = prefix_len % 8; + if (full_bytes > 0 && memcmp(a->s6_addr, b->s6_addr, (size_t)full_bytes) != 0) { + return 0; + } + if (remaining_bits == 0) { + return 1; + } + mask = (unsigned char)(0xffU << (8 - remaining_bits)); + return (a->s6_addr[full_bytes] & mask) == (b->s6_addr[full_bytes] & mask); +} + +static int TC_AUTO_IP_UNUSED get_iface_flags(int sockfd, const char *name, int *flags_out) { + struct ifreq flags_req; + + memset(&flags_req, 0, sizeof(flags_req)); + strncpy(flags_req.ifr_name, name, sizeof(flags_req.ifr_name) - 1); + if (ioctl(sockfd, SIOCGIFFLAGS, &flags_req) != 0) { + return -1; + } + *flags_out = flags_req.ifr_flags; + return 0; +} + +static uint32_t TC_AUTO_IP_UNUSED get_ipv4_netmask(int sockfd, const char *name, uint32_t ipv4_addr) { + struct ifreq mask_req; + + if (ipv4_is_link_local(ipv4_addr)) { + return ipv4_link_local_netmask(); + } + + memset(&mask_req, 0, sizeof(mask_req)); + strncpy(mask_req.ifr_name, name, sizeof(mask_req.ifr_name) - 1); + if (ioctl(sockfd, SIOCGIFNETMASK, &mask_req) == 0) { + struct sockaddr_in netmask_addr; + memset(&netmask_addr, 0, sizeof(netmask_addr)); + memcpy(&netmask_addr, &mask_req.ifr_addr, sizeof(netmask_addr)); + if (netmask_addr.sin_family == AF_INET) { + return netmask_addr.sin_addr.s_addr; + } + } + return 0; +} + +static int TC_AUTO_IP_UNUSED get_ipv6_prefix_len(int sockfd6, const char *name, const struct sockaddr_in6 *sin6) { +#if defined(SIOCGIFNETMASK_IN6) + struct in6_ifreq mask_req; + + if (sockfd6 < 0) { + return -1; + } + + memset(&mask_req, 0, sizeof(mask_req)); + strncpy(mask_req.ifr_name, name, sizeof(mask_req.ifr_name) - 1); + mask_req.ifr_ifru.ifru_addr = *sin6; + if (ioctl(sockfd6, SIOCGIFNETMASK_IN6, &mask_req) == 0 && + mask_req.ifr_ifru.ifru_addr.sin6_family == AF_INET6) { + return ipv6_prefix_length_from_mask(&mask_req.ifr_ifru.ifru_addr.sin6_addr); + } +#else + (void)sockfd6; + (void)name; + (void)sin6; +#endif + return -1; +} + static int TC_AUTO_IP_UNUSED iface_context_priority_score(const struct iface_context *ctx) { int score = 0; int prefix = netmask_prefix_length(ctx->netmask); @@ -271,6 +523,7 @@ static int TC_AUTO_IP_UNUSED append_iface_context(struct iface_context_set *out, } } if (out->count >= MAX_IFACE_CONTEXTS) { + out->truncated = 1; return 0; } @@ -312,44 +565,27 @@ static int TC_AUTO_IP_UNUSED collect_iface_contexts_with_policy(struct iface_con end = cursor + ifc.ifc_len; fixed_entries = ifreq_table_uses_fixed_entries((size_t)ifc.ifc_len); while (cursor < end) { - struct ifreq ifr_entry; - struct ifreq flags_req; - struct ifreq mask_req; + struct ifconf_entry_view entry; struct sockaddr_in sin; int flags; - uint32_t netmask = 0; size_t remaining = (size_t)(end - cursor); - size_t step; - if (copy_ifreq_entry(&ifr_entry, cursor, remaining) != 0) { - break; - } - step = ifreq_entry_size(&ifr_entry, remaining, fixed_entries); - if (step == 0) { + if (ifconf_entry_view_from_cursor(&entry, cursor, remaining, fixed_entries) != 0) { break; } - if (ifr_entry.ifr_addr.sa_family == AF_INET) { - memset(&flags_req, 0, sizeof(flags_req)); - strncpy(flags_req.ifr_name, ifr_entry.ifr_name, sizeof(flags_req.ifr_name) - 1); - if (ioctl(sockfd, SIOCGIFFLAGS, &flags_req) == 0) { - flags = flags_req.ifr_flags; - if (iface_flags_are_usable(flags, require_running)) { - memset(&mask_req, 0, sizeof(mask_req)); - strncpy(mask_req.ifr_name, ifr_entry.ifr_name, sizeof(mask_req.ifr_name) - 1); - if (ioctl(sockfd, SIOCGIFNETMASK, &mask_req) == 0) { - struct sockaddr_in netmask_addr; - memset(&netmask_addr, 0, sizeof(netmask_addr)); - memcpy(&netmask_addr, &mask_req.ifr_addr, sizeof(netmask_addr)); - netmask = netmask_addr.sin_addr.s_addr; - } - memset(&sin, 0, sizeof(sin)); - memcpy(&sin, &ifr_entry.ifr_addr, sizeof(sin)); - append_iface_context(out, ifr_entry.ifr_name, sin.sin_addr.s_addr, netmask, flags); - } - } + if (entry.addr->sa_family == AF_INET && entry.addr_len >= sizeof(sin) && + get_iface_flags(sockfd, entry.name, &flags) == 0 && + iface_flags_are_usable(flags, require_running)) { + memset(&sin, 0, sizeof(sin)); + memcpy(&sin, entry.addr, sizeof(sin)); + append_iface_context(out, + entry.name, + sin.sin_addr.s_addr, + get_ipv4_netmask(sockfd, entry.name, sin.sin_addr.s_addr), + flags); } - cursor += step; + cursor += entry.step; } close(sockfd); @@ -430,6 +666,409 @@ static int TC_AUTO_IP_UNUSED iface_context_cidr(char *out, size_t out_len, const return 0; } +static int TC_AUTO_IP_UNUSED link_context_priority_score(const struct link_context *ctx) { + int score = 0; + + if (ctx->ipv4_count > 0) { + if (ipv4_is_rfc1918(ctx->ipv4[0].addr)) { + score -= 4000; + } + if (ipv4_is_link_local(ctx->ipv4[0].addr)) { + score += 200; + } + } else { + score += 1000; + } + if (iface_name_is_strong_lan(ctx->name)) { + score -= 3000; + } else if (iface_name_is_likely_lan(ctx->name)) { + score -= 2500; + } + if (iface_name_is_likely_wan_or_tunnel(ctx->name)) { + score += 5000; + } + if ((ctx->flags & IFF_RUNNING) == 0) { + score += 500; + } + return score; +} + +static int TC_AUTO_IP_UNUSED link_context_compare(const struct link_context *a, const struct link_context *b) { + int a_score = link_context_priority_score(a); + int b_score = link_context_priority_score(b); + int name_cmp; + + if (a_score != b_score) { + return a_score < b_score ? -1 : 1; + } + name_cmp = strcmp(a->name, b->name); + if (name_cmp != 0) { + return name_cmp; + } + if (a->ipv4_count > 0 && b->ipv4_count > 0 && a->ipv4[0].addr != b->ipv4[0].addr) { + return ntohl(a->ipv4[0].addr) < ntohl(b->ipv4[0].addr) ? -1 : 1; + } + return 0; +} + +static void TC_AUTO_IP_UNUSED sort_link_contexts(struct link_context_set *set) { + size_t i; + + for (i = 1; i < set->count; i++) { + struct link_context current = set->links[i]; + size_t j = i; + while (j > 0 && link_context_compare(¤t, &set->links[j - 1]) < 0) { + set->links[j] = set->links[j - 1]; + j--; + } + set->links[j] = current; + } +} + +static struct link_context TC_AUTO_IP_UNUSED *find_or_add_link_context(struct link_context_set *out, + const char *name, + int flags) { + size_t i; + struct link_context *ctx; + + for (i = 0; i < out->count; i++) { + if (strcmp(out->links[i].name, name) == 0) { + if (out->links[i].flags == 0) { + out->links[i].flags = flags; + } + return &out->links[i]; + } + } + if (out->count >= MAX_IFACE_CONTEXTS) { + out->truncated = 1; + return NULL; + } + ctx = &out->links[out->count++]; + memset(ctx, 0, sizeof(*ctx)); + strncpy(ctx->name, name, sizeof(ctx->name) - 1); + ctx->flags = flags; + return ctx; +} + +static int TC_AUTO_IP_UNUSED link_context_has_ipv4(const struct link_context *ctx, uint32_t addr) { + size_t i; + + for (i = 0; i < ctx->ipv4_count; i++) { + if (ctx->ipv4[i].addr == addr) { + return 1; + } + } + return 0; +} + +static int TC_AUTO_IP_UNUSED link_context_has_ipv6(const struct link_context *ctx, const struct in6_addr *addr) { + size_t i; + + for (i = 0; i < ctx->ipv6_count; i++) { + if (memcmp(&ctx->ipv6[i].addr, addr, sizeof(*addr)) == 0) { + return 1; + } + } + return 0; +} + +static int TC_AUTO_IP_UNUSED append_link_ipv4(struct link_context_set *out, + const char *name, + uint32_t ipv4_addr, + uint32_t netmask, + int flags) { + struct link_context *ctx; + size_t pos; + + if (!runtime_ipv4_is_bindable(ipv4_addr)) { + return 0; + } + ctx = find_or_add_link_context(out, name, flags); + if (ctx == NULL || link_context_has_ipv4(ctx, ipv4_addr)) { + return 0; + } + if (ctx->ipv4_count >= MAX_LINK_IPV4_ADDRS) { + out->truncated = 1; + return 0; + } + pos = ctx->ipv4_count++; + ctx->ipv4[pos].addr = ipv4_addr; + ctx->ipv4[pos].netmask = netmask; + ctx->mdns_ipv4_transport = 1; + if (ctx->ipv4_count == 1) { + ctx->flags = flags; + } + return 1; +} + +static int TC_AUTO_IP_UNUSED append_link_ipv6_with_transport(struct link_context_set *out, + const char *name, + const struct in6_addr *addr, + int prefix_len, + unsigned int scope_id, + int flags, + int mdns_ipv6_transport) { + struct link_context *ctx; + size_t pos; + + if (!runtime_ipv6_is_usable(addr)) { + return 0; + } + ctx = find_or_add_link_context(out, name, flags); + if (ctx == NULL || link_context_has_ipv6(ctx, addr)) { + return 0; + } + if (ctx->ipv6_count >= MAX_LINK_IPV6_ADDRS) { + out->truncated = 1; + return 0; + } + if (ctx->ifindex == 0 && scope_id != 0) { + ctx->ifindex = scope_id; + } + if (mdns_ipv6_transport) { + ctx->mdns_ipv6_transport = 1; + } + pos = ctx->ipv6_count++; + ctx->ipv6[pos].addr = *addr; + ctx->ipv6[pos].scope_id = scope_id; + ctx->ipv6[pos].prefix_len = prefix_len >= 0 && prefix_len <= 128 ? prefix_len : -1; + ctx->ipv6[pos].link_local = ipv6_is_link_local_addr(addr); + return 1; +} + +static int TC_AUTO_IP_UNUSED append_link_ipv6(struct link_context_set *out, + const char *name, + const struct in6_addr *addr, + int prefix_len, + unsigned int scope_id, + int flags) { + return append_link_ipv6_with_transport(out, name, addr, prefix_len, scope_id, flags, 1); +} + +static int TC_AUTO_IP_UNUSED link_ipv6_addr_is_samba_bindable(const struct link_ipv6_addr *addr) { + return addr->prefix_len >= 0 && runtime_ipv6_is_bindable(&addr->addr); +} + +static int TC_AUTO_IP_UNUSED link_context_has_samba_address(const struct link_context *ctx) { + size_t i; + + if (ctx->ipv4_count > 0) { + return 1; + } + for (i = 0; i < ctx->ipv6_count; i++) { + if (link_ipv6_addr_is_samba_bindable(&ctx->ipv6[i])) { + return 1; + } + } + return 0; +} + +static int TC_AUTO_IP_UNUSED link_context_has_advertisable_ipv4(const struct link_context *ctx) { + return ctx->ipv4_count > 0; +} + +static int TC_AUTO_IP_UNUSED link_context_has_advertisable_ipv6(const struct link_context *ctx) { + size_t i; + + for (i = 0; i < ctx->ipv6_count; i++) { + if (link_ipv6_addr_is_samba_bindable(&ctx->ipv6[i])) { + return 1; + } + } + return 0; +} + +static int TC_AUTO_IP_UNUSED link_context_has_advertisable_address(const struct link_context *ctx) { + return link_context_has_advertisable_ipv4(ctx) || + link_context_has_advertisable_ipv6(ctx); +} + +static int TC_AUTO_IP_UNUSED link_context_has_mdns_ipv4_transport(const struct link_context *ctx) { + return ctx->mdns_ipv4_transport && ctx->ipv4_count > 0; +} + +static void TC_AUTO_IP_UNUSED disable_link_contexts_mdns_ipv4_transport(struct link_context_set *set) { + size_t i; + + for (i = 0; i < set->count; i++) { + set->links[i].mdns_ipv4_transport = 0; + set->links[i].mdns_ipv4_transport_addr = 0; + } +} + +static int TC_AUTO_IP_UNUSED link_context_has_mdns_ipv6_transport(const struct link_context *ctx) { + return ctx->mdns_ipv6_transport && ctx->ifindex != 0 && ctx->ipv6_count > 0; +} + +static void TC_AUTO_IP_UNUSED disable_link_contexts_mdns_ipv6_transport(struct link_context_set *set) { + size_t i; + + for (i = 0; i < set->count; i++) { + set->links[i].mdns_ipv6_transport = 0; + } +} + +static int TC_AUTO_IP_UNUSED link_context_is_advertise_eligible(const struct link_context *ctx) { + return link_context_has_advertisable_address(ctx); +} + +static void TC_AUTO_IP_UNUSED filter_advertise_link_contexts(struct link_context_set *out, + const struct link_context_set *in) { + size_t i; + + memset(out, 0, sizeof(*out)); + for (i = 0; i < in->count; i++) { + if (!link_context_is_advertise_eligible(&in->links[i])) { + continue; + } + if (out->count >= MAX_IFACE_CONTEXTS) { + out->truncated = 1; + break; + } + out->links[out->count++] = in->links[i]; + } + sort_link_contexts(out); +} + +static int TC_AUTO_IP_UNUSED link_context_identity_equal(const struct link_context *a, + const struct link_context *b) { + size_t i; + + if (strcmp(a->name, b->name) != 0 || + a->flags != b->flags || + a->ifindex != b->ifindex || + a->mdns_ipv4_transport != b->mdns_ipv4_transport || + a->mdns_ipv6_transport != b->mdns_ipv6_transport || + a->ipv4_count != b->ipv4_count || + a->ipv6_count != b->ipv6_count) { + return 0; + } + for (i = 0; i < a->ipv4_count; i++) { + if (a->ipv4[i].addr != b->ipv4[i].addr || + a->ipv4[i].netmask != b->ipv4[i].netmask) { + return 0; + } + } + for (i = 0; i < a->ipv6_count; i++) { + if (memcmp(&a->ipv6[i].addr, &b->ipv6[i].addr, sizeof(a->ipv6[i].addr)) != 0 || + a->ipv6[i].scope_id != b->ipv6[i].scope_id || + a->ipv6[i].prefix_len != b->ipv6[i].prefix_len || + a->ipv6[i].link_local != b->ipv6[i].link_local) { + return 0; + } + } + return 1; +} + +static int TC_AUTO_IP_UNUSED link_context_set_contains(const struct link_context_set *set, + const struct link_context *ctx) { + size_t i; + + for (i = 0; i < set->count; i++) { + if (link_context_identity_equal(&set->links[i], ctx)) { + return 1; + } + } + return 0; +} + +static int TC_AUTO_IP_UNUSED link_context_sets_equal(const struct link_context_set *a, + const struct link_context_set *b) { + size_t i; + + if (a->count != b->count) { + return 0; + } + for (i = 0; i < a->count; i++) { + if (!link_context_set_contains(b, &a->links[i])) { + return 0; + } + } + return 1; +} + +static int TC_AUTO_IP_UNUSED link_context_ipv4_cidr(char *out, + size_t out_len, + const struct link_ipv4_addr *addr) { + char ip_buf[INET_ADDRSTRLEN]; + int written; + + written = snprintf(out, + out_len, + "%s/%d", + ipv4_to_string(addr->addr, ip_buf, sizeof(ip_buf)), + netmask_prefix_length(addr->netmask)); + if (written < 0 || (size_t)written >= out_len) { + return -1; + } + return 0; +} + +static int TC_AUTO_IP_UNUSED link_context_ipv6_cidr(char *out, + size_t out_len, + const struct link_ipv6_addr *addr) { + char ip_buf[INET6_ADDRSTRLEN]; + int written; + + if (inet_ntop(AF_INET6, &addr->addr, ip_buf, sizeof(ip_buf)) == NULL) { + return -1; + } + if (addr->prefix_len < 0 || addr->prefix_len > 128) { + return -1; + } + written = snprintf(out, out_len, "%s/%d", ip_buf, addr->prefix_len); + if (written < 0 || (size_t)written >= out_len) { + return -1; + } + return 0; +} + +static int TC_AUTO_IP_UNUSED print_smb_link_bind_tokens(FILE *stream, const struct link_context_set *set) { + int wrote = 0; + size_t i; + + for (i = 0; i < set->count; i++) { + size_t j; + const struct link_context *ctx = &set->links[i]; + for (j = 0; j < ctx->ipv4_count; j++) { + char cidr[INET_ADDRSTRLEN + 4]; + if (link_context_ipv4_cidr(cidr, sizeof(cidr), &ctx->ipv4[j]) != 0) { + return -1; + } + if (wrote && fputc(' ', stream) == EOF) { + return -1; + } + if (fputs(cidr, stream) == EOF) { + return -1; + } + wrote = 1; + } + for (j = 0; j < ctx->ipv6_count; j++) { + char cidr[INET6_ADDRSTRLEN + 5]; + if (!link_ipv6_addr_is_samba_bindable(&ctx->ipv6[j])) { + continue; + } + if (link_context_ipv6_cidr(cidr, sizeof(cidr), &ctx->ipv6[j]) != 0) { + return -1; + } + if (wrote && fputc(' ', stream) == EOF) { + return -1; + } + if (fputs(cidr, stream) == EOF) { + return -1; + } + wrote = 1; + } + } + if (!wrote) { + return -1; + } + if (fputc('\n', stream) == EOF) { + return -1; + } + return 0; +} + static int TC_AUTO_IP_UNUSED print_iface_context_cidrs(FILE *stream, const struct iface_context_set *set) { size_t i; @@ -454,6 +1093,178 @@ static int TC_AUTO_IP_UNUSED print_iface_context_cidrs(FILE *stream, const struc return 0; } +static int TC_AUTO_IP_UNUSED collect_link_contexts_with_policy(struct link_context_set *out, int require_running) { + int sockfd = -1; + int sockfd6 = -1; + char buffer[8192]; + struct ifconf ifc; + char *cursor; + char *end; + int fixed_entries; + + memset(out, 0, sizeof(*out)); + + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) { + perror("socket interface enumeration"); + return -1; + } + sockfd6 = socket(AF_INET6, SOCK_DGRAM, 0); + + memset(&ifc, 0, sizeof(ifc)); + ifc.ifc_len = sizeof(buffer); + ifc.ifc_buf = buffer; + if (ioctl(sockfd, SIOCGIFCONF, &ifc) < 0) { + perror("ioctl(SIOCGIFCONF)"); + if (sockfd6 >= 0) { + close(sockfd6); + } + close(sockfd); + return -1; + } + + cursor = ifc.ifc_buf; + end = cursor + ifc.ifc_len; + fixed_entries = ifreq_table_uses_fixed_entries((size_t)ifc.ifc_len); + while (cursor < end) { + struct ifconf_entry_view entry; + int flags = 0; + size_t remaining = (size_t)(end - cursor); + + if (ifconf_entry_view_from_cursor(&entry, cursor, remaining, fixed_entries) != 0) { + break; + } + + if (get_iface_flags(sockfd, entry.name, &flags) != 0) { + cursor += entry.step; + continue; + } + if (!iface_flags_are_usable(flags, require_running)) { + cursor += entry.step; + continue; + } + + if (entry.addr->sa_family == AF_INET && entry.addr_len >= sizeof(struct sockaddr_in)) { + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + memcpy(&sin, entry.addr, sizeof(sin)); + append_link_ipv4(out, + entry.name, + sin.sin_addr.s_addr, + get_ipv4_netmask(sockfd, entry.name, sin.sin_addr.s_addr), + flags); + } else if (entry.addr->sa_family == AF_INET6 && entry.addr_len >= sizeof(struct sockaddr_in6)) { + struct sockaddr_in6 sin6; + int prefix_len; + + memset(&sin6, 0, sizeof(sin6)); + memcpy(&sin6, entry.addr, sizeof(sin6)); + prefix_len = get_ipv6_prefix_len(sockfd6, entry.name, &sin6); + append_link_ipv6_with_transport(out, + entry.name, + &sin6.sin6_addr, + prefix_len, + (unsigned int)sin6.sin6_scope_id, + flags, + sockfd6 >= 0); +#if defined(AF_LINK) && (defined(__NetBSD__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)) + } else if (entry.addr->sa_family == AF_LINK && entry.addr_len >= sizeof(struct sockaddr_dl)) { + const struct sockaddr_dl *sdl = (const struct sockaddr_dl *)entry.addr; + struct link_context *ctx = find_or_add_link_context(out, entry.name, flags); + if (ctx != NULL && ctx->ifindex == 0) { + ctx->ifindex = (unsigned int)sdl->sdl_index; + } +#endif + } + cursor += entry.step; + } + + if (sockfd6 >= 0) { + close(sockfd6); + } + close(sockfd); + { + size_t i; + size_t write_i = 0; + for (i = 0; i < out->count; i++) { + if (out->links[i].ipv4_count == 0 && out->links[i].ipv6_count == 0) { + continue; + } + if (out->links[i].ifindex == 0) { + out->links[i].ifindex = if_nametoindex(out->links[i].name); + } + if (write_i != i) { + out->links[write_i] = out->links[i]; + } + write_i++; + } + out->count = write_i; + } + sort_link_contexts(out); + return 0; +} + +static int TC_AUTO_IP_UNUSED collect_usable_link_contexts(struct link_context_set *out) { + if (collect_link_contexts_with_policy(out, 1) != 0) { + return -1; + } + if (out->count > 0) { + return 0; + } + if (collect_link_contexts_with_policy(out, 0) != 0) { + return -1; + } + if (out->count > 0) { + fprintf(stderr, "auto-ip: no IFF_RUNNING usable address links found; using IFF_UP fallback links\n"); + } + return 0; +} + +static void TC_AUTO_IP_UNUSED log_link_contexts(const char *prefix, const struct link_context_set *set) { + size_t i; + + fprintf(stderr, "%s: links=%lu\n", prefix, (unsigned long)set->count); + for (i = 0; i < set->count; i++) { + size_t j; + char transport_ip_buf[INET_ADDRSTRLEN]; + const struct link_context *ctx = &set->links[i]; + fprintf(stderr, "%s: link[%lu] iface=%s flags=0x%x ifindex=%u mdns_ipv4=%d mdns_ipv4_addr=%s mdns_ipv6=%d score=%d ipv4=%lu ipv6=%lu\n", + prefix, + (unsigned long)i, + ctx->name, + (unsigned int)ctx->flags, + ctx->ifindex, + ctx->mdns_ipv4_transport, + ctx->mdns_ipv4_transport_addr != 0 ? ipv4_to_string(ctx->mdns_ipv4_transport_addr, transport_ip_buf, sizeof(transport_ip_buf)) : "(auto)", + ctx->mdns_ipv6_transport, + link_context_priority_score(ctx), + (unsigned long)ctx->ipv4_count, + (unsigned long)ctx->ipv6_count); + for (j = 0; j < ctx->ipv4_count; j++) { + char ip_buf[INET_ADDRSTRLEN]; + char mask_buf[INET_ADDRSTRLEN]; + fprintf(stderr, "%s: link[%lu].ipv4[%lu]=%s netmask=%s\n", + prefix, + (unsigned long)i, + (unsigned long)j, + ipv4_to_string(ctx->ipv4[j].addr, ip_buf, sizeof(ip_buf)), + ipv4_to_string(ctx->ipv4[j].netmask, mask_buf, sizeof(mask_buf))); + } + for (j = 0; j < ctx->ipv6_count; j++) { + char ip_buf[INET6_ADDRSTRLEN]; + const char *printed = inet_ntop(AF_INET6, &ctx->ipv6[j].addr, ip_buf, sizeof(ip_buf)); + fprintf(stderr, "%s: link[%lu].ipv6[%lu]=%s/%d scope=%u link_local=%d\n", + prefix, + (unsigned long)i, + (unsigned long)j, + printed != NULL ? printed : "invalid", + ctx->ipv6[j].prefix_len, + ctx->ipv6[j].scope_id, + ctx->ipv6[j].link_local); + } + } +} + static void TC_AUTO_IP_UNUSED log_iface_contexts(const char *prefix, const struct iface_context_set *set) { size_t i; diff --git a/build/mdns-advertiser.c b/build/mdns-advertiser.c index d430989..6c3e327 100644 --- a/build/mdns-advertiser.c +++ b/build/mdns-advertiser.c @@ -27,11 +27,12 @@ #define MDNS_PORT 5353 #endif #define MDNS_GROUP "224.0.0.251" +#define MDNS_GROUP_V6 "ff02::fb" #define BUF_SIZE 1500 #define MAX_NAME 256 #define MAX_LABEL 63 #define MAX_TXT_STRING 255 -#define ANNOUNCE_INTERVAL 30 +#define STARTUP_BURST_COUNT 4 #define MODEL_TXT_PREFIX "model=" #define ADISK_DEFAULT_DISK_KEY "dk0" #define ADISK_SYS_ADVF "0x1010" @@ -59,12 +60,12 @@ #define SNAPSHOT_CAPTURE_STEP_SECONDS 5 #endif #define TAKEOVER_RETRY_COUNT 6 -#define STARTUP_BURST_COUNT 7 #define MAX_IFACE_CONTEXTS 16 #define AUTO_IP_STABILIZE_SECONDS 3 #define AUTO_IP_STARTUP_POLL_SECONDS 2 #define AUTO_IP_STABLE_POLL_SECONDS 30 #define ADVERTISER_VERSION_CODE 2104 +#define DNS_SD_SERVICE_ENUMERATION_NAME "_services._dns-sd._udp.local." #define DNS_TYPE_A 1 #define DNS_TYPE_PTR 12 @@ -73,15 +74,38 @@ #define DNS_TYPE_SRV 33 #define DNS_TYPE_ANY 255 #define DNS_CLASS_IN 1 +#define DNS_CLASS_ANY 255 #define DNS_CLASS_CACHE_FLUSH 0x8000 +#define DNS_CLASS_QU 0x8000 #define DNS_CLASS_IN_UNIQUE (DNS_CLASS_IN | DNS_CLASS_CACHE_FLUSH) #define MDNS_REPLY_UNICAST 1 #define MDNS_REPLY_MULTICAST 2 +#define MDNS_REPLY_LEGACY_UNICAST 4 #define DNS_FLAG_QR 0x8000 +#define DNS_FLAG_TC 0x0200 #define DNS_FLAG_AA 0x0400 +#define LEGACY_UNICAST_TTL_MAX 10 +#define TC_KNOWN_ANSWER_DEFER_MS 450 +#define MDNS_MULTICAST_RESPONSE_DELAY_MIN_MS 20 +#define MDNS_MULTICAST_RESPONSE_DELAY_MAX_MS 120 +#define PLANNED_RR_MAX 192 +#define PLANNED_RDATA_MAX 1024 + +#if !defined(IPV6_JOIN_GROUP) && defined(IPV6_ADD_MEMBERSHIP) +#define IPV6_JOIN_GROUP IPV6_ADD_MEMBERSHIP +#endif +#if !defined(IPV6_LEAVE_GROUP) && defined(IPV6_DROP_MEMBERSHIP) +#define IPV6_LEAVE_GROUP IPV6_DROP_MEMBERSHIP +#endif static volatile sig_atomic_t g_stop = 0; +#if defined(__GNUC__) +#define MDNS_UNUSED __attribute__((unused)) +#else +#define MDNS_UNUSED +#endif + #ifndef TC_VA_COPY #if defined(va_copy) #define TC_VA_COPY(dst, src) va_copy(dst, src) @@ -126,50 +150,25 @@ static int timestamped_write_message(FILE *stream, const char *message) { static int timestamped_vfprintf(FILE *stream, const char *format, va_list ap) { char stack_message[4096]; - char *message = stack_message; - va_list sizing_ap; - va_list format_ap; int result; if (stream != stderr && stream != stdout) { return vfprintf(stream, format, ap); } - TC_VA_COPY(sizing_ap, ap); - result = vsnprintf(stack_message, sizeof(stack_message), format, sizing_ap); - va_end(sizing_ap); + result = vsnprintf(stack_message, sizeof(stack_message), format, ap); if (result < 0) { return result; } - if ((size_t)result >= sizeof(stack_message)) { - size_t message_size = (size_t)result + 1; - message = malloc(message_size); - if (message == NULL) { - (void)timestamped_write_message(stream, stack_message); - (void)timestamped_write_message(stream, "\n[log message truncated: allocation failed]\n"); - fflush(stream); - return result; - } - TC_VA_COPY(format_ap, ap); - result = vsnprintf(message, message_size, format, format_ap); - va_end(format_ap); - if (result < 0) { - free(message); - return result; - } + stack_message[sizeof(stack_message) - 2] = '\n'; + stack_message[sizeof(stack_message) - 1] = '\0'; } - if (timestamped_write_message(stream, message) != 0) { - if (message != stack_message) { - free(message); - } + if (timestamped_write_message(stream, stack_message) != 0) { return -1; } fflush(stream); - if (message != stack_message) { - free(message); - } return result; } @@ -290,6 +289,7 @@ struct service_record { struct service_record_set { struct service_record records[SNAPSHOT_MAX_RECORDS]; size_t count; + int truncated; }; struct service_type_set { @@ -297,26 +297,65 @@ struct service_type_set { size_t count; }; -struct query_answer_routes { - int smb_ptr; - int smb_srv; - int smb_txt; - int host_a; - int adisk_ptr; - int adisk_srv; - int adisk_txt; - int device_info_ptr; - int device_info_srv; - int device_info_txt; - int airport_ptr; - int airport_srv; - int airport_txt; - int snapshot_ptr[SNAPSHOT_MAX_RECORDS]; - int snapshot_srv[SNAPSHOT_MAX_RECORDS]; - int snapshot_txt[SNAPSHOT_MAX_RECORDS]; - int snapshot_a[SNAPSHOT_MAX_RECORDS]; +struct mdns_socket_pair { + int ipv4_fd; + int ipv6_fd; +}; + +struct mdns_membership_delta { + uint32_t ipv4[MAX_IFACE_CONTEXTS]; + size_t ipv4_count; + unsigned int ipv6_ifindex[MAX_IFACE_CONTEXTS]; + char ipv6_name[MAX_IFACE_CONTEXTS][IFNAMSIZ]; + size_t ipv6_count; +}; + + +struct planned_rr { + char owner[MAX_NAME]; + uint16_t type; + uint16_t rrclass; + uint32_t ttl; + uint8_t rdata[PLANNED_RDATA_MAX]; + uint16_t rdlength; + int routes; +}; + +struct planned_rr_set { + struct planned_rr records[PLANNED_RR_MAX]; + size_t count; + int truncated; +}; + +struct response_question_section { + const uint8_t *bytes; + size_t len; + uint16_t count; +}; + +struct stored_question_section { + uint8_t bytes[BUF_SIZE]; + size_t len; + uint16_t count; +}; + +struct deferred_response { + int active; + int sockfd; + long long due_ms; + uint16_t response_id; + int use_snapshot_records; + struct sockaddr_storage multicast_dest; + socklen_t multicast_dest_len; + struct sockaddr_storage source; + socklen_t source_len; + struct stored_question_section questions; + struct planned_rr_set planned; }; +static struct deferred_response g_deferred_response; +static const unsigned int g_startup_burst_offsets_ms[STARTUP_BURST_COUNT] = {0, 1000, 3000, 7000}; + static int name_equals(const char *a, const char *b); static int build_instance_fqdn(char *out, size_t out_len, const char *instance_name, const char *service_type); static int open_mdns_socket(int shared_bind, int log_bind_errors, uint32_t ipv4_addr, const char *socket_role); @@ -332,6 +371,7 @@ static int add_rr_txt_strings(uint8_t *buf, size_t *off, size_t cap, const char const char **strings, size_t string_count); static int add_rr_srv(uint8_t *buf, size_t *off, size_t cap, const char *owner, const char *target, uint16_t port, uint32_t ttl); static int add_rr_a(uint8_t *buf, size_t *off, size_t cap, const char *owner, uint32_t ipv4_addr, uint32_t ttl); +static int add_rr_aaaa(uint8_t *buf, size_t *off, size_t cap, const char *owner, const struct in6_addr *ipv6_addr, uint32_t ttl); struct dns_header { uint16_t id; @@ -360,12 +400,64 @@ static const char *ipv4_to_string(uint32_t ipv4_addr, char *out, size_t out_len) #include "auto-ip-common.inc" +static uint32_t link_preferred_ipv4_source(const struct link_context *link); +static int link_set_has_ipv4_membership(const struct link_context_set *set, uint32_t ipv4_addr); +static int source_matches_link_ipv4_subnet(uint32_t source_ipv4_addr, const struct link_context *link); +static int flush_deferred_response_if_due(long long now_ms); +static long long deferred_response_adjust_wait_ms(long long now_ms, long long wait_ms); +static void clear_deferred_response_for_sockfd(int sockfd); + typedef int (*mdns_collect_iface_contexts_fn)(struct iface_context_set *out, void *userdata); +typedef int (*mdns_collect_link_contexts_fn)(struct link_context_set *out, void *userdata); typedef void (*mdns_sleep_fn)(unsigned int seconds, void *userdata); +static void derive_usable_iface_contexts_from_links(struct iface_context_set *out, + const struct link_context_set *links) { + size_t i; + + memset(out, 0, sizeof(*out)); + out->truncated = links->truncated; + for (i = 0; i < links->count; i++) { + size_t j; + const struct link_context *link = &links->links[i]; + for (j = 0; j < link->ipv4_count; j++) { + (void)append_iface_context(out, + link->name, + link->ipv4[j].addr, + link->ipv4[j].netmask, + link->flags); + } + } + sort_iface_contexts(out); +} + static int collect_usable_iface_contexts_provider(struct iface_context_set *out, void *userdata) { + struct link_context_set links; + + (void)userdata; + memset(&links, 0, sizeof(links)); + if (collect_usable_link_contexts(&links) != 0) { + return -1; + } + derive_usable_iface_contexts_from_links(out, &links); + return 0; +} + +static int collect_usable_link_contexts_provider(struct link_context_set *out, void *userdata) { + (void)userdata; + return collect_usable_link_contexts(out); +} + +static int collect_usable_advertise_link_contexts_provider(struct link_context_set *out, void *userdata) { + struct link_context_set all_links; + (void)userdata; - return collect_usable_iface_contexts(out); + memset(&all_links, 0, sizeof(all_links)); + if (collect_usable_link_contexts(&all_links) != 0) { + return -1; + } + filter_advertise_link_contexts(out, &all_links); + return 0; } static void mdns_sleep_provider(unsigned int seconds, void *userdata) { @@ -402,7 +494,7 @@ static int wait_for_auto_iface_contexts_with_provider(struct iface_context_set * return -1; } -static int wait_for_auto_iface_contexts(struct iface_context_set *out, const char *role) { +static int MDNS_UNUSED wait_for_auto_iface_contexts(struct iface_context_set *out, const char *role) { return wait_for_auto_iface_contexts_with_provider(out, role, collect_usable_iface_contexts_provider, @@ -410,6 +502,51 @@ static int wait_for_auto_iface_contexts(struct iface_context_set *out, const cha NULL); } +static int wait_for_auto_link_contexts_with_provider(struct link_context_set *out, + const char *role, + mdns_collect_link_contexts_fn collect_contexts, + mdns_sleep_fn sleep_fn, + void *userdata) { + struct link_context_set first; + + if (collect_contexts == NULL || sleep_fn == NULL) { + return -1; + } + + memset(out, 0, sizeof(*out)); + while (!g_stop) { + memset(&first, 0, sizeof(first)); + if (collect_contexts(&first, userdata) == 0 && first.count > 0) { + fprintf(stderr, "%s auto-ip: first usable address link observed; waiting %ds for network stabilization\n", + role, AUTO_IP_STABILIZE_SECONDS); + sleep_fn(AUTO_IP_STABILIZE_SECONDS, userdata); + if (collect_contexts(out, userdata) == 0 && out->count > 0) { + sort_link_contexts(out); + return 0; + } + fprintf(stderr, "%s auto-ip: usable address links disappeared during stabilization; retrying\n", role); + } + sleep_fn(AUTO_IP_STARTUP_POLL_SECONDS, userdata); + } + return -1; +} + +static int wait_for_auto_link_contexts(struct link_context_set *out, const char *role) { + return wait_for_auto_link_contexts_with_provider(out, + role, + collect_usable_link_contexts_provider, + mdns_sleep_provider, + NULL); +} + +static int wait_for_auto_advertise_link_contexts(struct link_context_set *out, const char *role) { + return wait_for_auto_link_contexts_with_provider(out, + role, + collect_usable_advertise_link_contexts_provider, + mdns_sleep_provider, + NULL); +} + static int print_auto_ip_cidrs_with_provider(FILE *stream, mdns_collect_iface_contexts_fn collect_contexts, void *userdata) { @@ -426,6 +563,10 @@ static int print_auto_ip_cidrs_with_provider(FILE *stream, if (contexts.count == 0) { return EXIT_AUTO_IP_UNAVAILABLE; } + if (contexts.truncated) { + fprintf(stderr, "auto-ip: usable IPv4 context list exceeded static capacity\n"); + return EXIT_AUTO_IP_PROBE_FAILED; + } sort_iface_contexts(&contexts); if (print_iface_context_cidrs(stream, &contexts) != 0) { return EXIT_AUTO_IP_PROBE_FAILED; @@ -433,6 +574,111 @@ static int print_auto_ip_cidrs_with_provider(FILE *stream, return EXIT_OK; } +static int link_contexts_need_ipv4_socket(const struct link_context_set *set); +static int link_contexts_need_ipv6_socket(const struct link_context_set *set); + +static int print_smb_bind_interfaces_with_provider(FILE *stream, + mdns_collect_link_contexts_fn collect_contexts, + void *userdata) { + struct link_context_set links; + size_t i; + int has_samba_address = 0; + + if (collect_contexts == NULL) { + return EXIT_AUTO_IP_PROBE_FAILED; + } + + memset(&links, 0, sizeof(links)); + if (collect_contexts(&links, userdata) != 0) { + return EXIT_AUTO_IP_PROBE_FAILED; + } + if (links.count == 0) { + return EXIT_AUTO_IP_UNAVAILABLE; + } + sort_link_contexts(&links); + for (i = 0; i < links.count; i++) { + if (link_context_has_samba_address(&links.links[i])) { + has_samba_address = 1; + break; + } + } + if (!has_samba_address) { + return EXIT_AUTO_IP_UNAVAILABLE; + } + if (links.truncated) { + fprintf(stderr, "auto-ip: Samba bind interface list exceeded static capacity\n"); + return EXIT_AUTO_IP_PROBE_FAILED; + } + if (print_smb_link_bind_tokens(stream, &links) != 0) { + return EXIT_AUTO_IP_PROBE_FAILED; + } + return EXIT_OK; +} + +static int print_mdns_socket_families_with_provider(FILE *stream, + mdns_collect_link_contexts_fn collect_contexts, + void *userdata) { + struct link_context_set all_links; + struct link_context_set links; + int need_ipv4; + int need_ipv6; + + if (collect_contexts == NULL) { + return EXIT_AUTO_IP_PROBE_FAILED; + } + + memset(&all_links, 0, sizeof(all_links)); + memset(&links, 0, sizeof(links)); + if (collect_contexts(&all_links, userdata) != 0) { + return EXIT_AUTO_IP_PROBE_FAILED; + } + if (all_links.count == 0) { + return EXIT_AUTO_IP_UNAVAILABLE; + } + filter_advertise_link_contexts(&links, &all_links); + if (all_links.truncated || links.truncated) { + fprintf(stderr, "auto-ip: mDNS socket family link list exceeded static capacity\n"); + return EXIT_AUTO_IP_PROBE_FAILED; + } + if (links.count == 0) { + return EXIT_AUTO_IP_UNAVAILABLE; + } + sort_link_contexts(&links); + need_ipv4 = link_contexts_need_ipv4_socket(&links); + need_ipv6 = link_contexts_need_ipv6_socket(&links); + if (!need_ipv4 && !need_ipv6) { + return EXIT_AUTO_IP_UNAVAILABLE; + } + if (need_ipv4 && fputs("ipv4", stream) == EOF) { + return EXIT_AUTO_IP_PROBE_FAILED; + } + if (need_ipv6) { + if (need_ipv4 && fputc(' ', stream) == EOF) { + return EXIT_AUTO_IP_PROBE_FAILED; + } + if (fputs("ipv6", stream) == EOF) { + return EXIT_AUTO_IP_PROBE_FAILED; + } + } + if (fputc('\n', stream) == EOF) { + return EXIT_AUTO_IP_PROBE_FAILED; + } + return EXIT_OK; +} + +static int open_dualstack_mdns_sockets(int shared_bind, + struct link_context_set *links, + int log_bind_errors, + struct mdns_socket_pair *out); +static void close_mdns_socket_pair(struct mdns_socket_pair *sockets); +static int set_outbound_multicast_interface(int sockfd, uint32_t ipv4_addr, const char *socket_role, + int log_success, int log_errors); +static int set_outbound_multicast_interface6(int sockfd, unsigned int ifindex, const char *socket_role, + int log_success, int log_errors); +static void drop_mdns_multicast_group_best_effort(int sockfd, uint32_t ipv4_addr, const char *socket_role); +static void drop_mdns_multicast_group6_best_effort(int sockfd, unsigned int ifindex, const char *ifname, + const char *socket_role); + static void log_startup_config(const struct config *cfg, int shared_bind, int auto_ip) { char ipv4_buf[INET_ADDRSTRLEN]; @@ -595,6 +841,7 @@ static struct service_record *find_or_add_record(struct service_record_set *set, return record; } if (set->count >= SNAPSHOT_MAX_RECORDS) { + set->truncated = 1; return NULL; } record = &set->records[set->count++]; @@ -665,10 +912,14 @@ static void usage(const char *prog) { " %s --save-snapshot [--save-all-snapshot ] [airport identity options]\n" " %s --save-airport-snapshot --instance --host