+
+static int on_llmnr_stream_packet(DnsStream *s) {
+ assert(s);
+
+ if (dns_packet_validate_query(s->read_packet) > 0) {
+ Link *l;
+
+ l = hashmap_get(s->manager->links, INT_TO_PTR(s->read_packet->ifindex));
+ if (l) {
+ DnsScope *scope = NULL;
+
+ if (s->read_packet->family == AF_INET)
+ scope = l->llmnr_ipv4_scope;
+ else if (s->read_packet->family == AF_INET6)
+ scope = l->llmnr_ipv6_scope;
+
+ if (scope) {
+ dns_scope_process_query(scope, s, s->read_packet);
+
+ /* If no reply packet was set, we free the stream */
+ if (s->write_packet)
+ return 0;
+ }
+ }
+ }
+
+ dns_stream_free(s);
+ return 0;
+}
+
+static int on_llmnr_stream(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ DnsStream *stream;
+ Manager *m = userdata;
+ int cfd, r;
+
+ cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
+ if (cfd < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ return 0;
+
+ return -errno;
+ }
+
+ r = dns_stream_new(m, &stream, DNS_PROTOCOL_LLMNR, cfd);
+ if (r < 0) {
+ safe_close(cfd);
+ return r;
+ }
+
+ stream->on_packet = on_llmnr_stream_packet;
+ return 0;
+}
+
+int manager_llmnr_ipv4_tcp_fd(Manager *m) {
+ union sockaddr_union sa = {
+ .in.sin_family = AF_INET,
+ .in.sin_port = htobe16(5355),
+ };
+ static const int one = 1, pmtu = IP_PMTUDISC_DONT;
+ int r;
+
+ assert(m);
+
+ if (m->llmnr_ipv4_tcp_fd >= 0)
+ return m->llmnr_ipv4_tcp_fd;
+
+ m->llmnr_ipv4_tcp_fd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (m->llmnr_ipv4_tcp_fd < 0)
+ return -errno;
+
+ /* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */
+ r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_TTL, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->llmnr_ipv4_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ /* Disable Don't-Fragment bit in the IP header */
+ r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = bind(m->llmnr_ipv4_tcp_fd, &sa.sa, sizeof(sa.in));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = listen(m->llmnr_ipv4_tcp_fd, SOMAXCONN);
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = sd_event_add_io(m->event, &m->llmnr_ipv4_tcp_event_source, m->llmnr_ipv4_tcp_fd, EPOLLIN, on_llmnr_stream, m);
+ if (r < 0)
+ goto fail;
+
+ return m->llmnr_ipv4_tcp_fd;
+
+fail:
+ m->llmnr_ipv4_tcp_fd = safe_close(m->llmnr_ipv4_tcp_fd);
+ return r;
+}
+
+int manager_llmnr_ipv6_tcp_fd(Manager *m) {
+ union sockaddr_union sa = {
+ .in6.sin6_family = AF_INET6,
+ .in6.sin6_port = htobe16(5355),
+ };
+ static const int one = 1;
+ int r;
+
+ assert(m);
+
+ if (m->llmnr_ipv6_tcp_fd >= 0)
+ return m->llmnr_ipv6_tcp_fd;
+
+ m->llmnr_ipv6_tcp_fd = socket(AF_INET6, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (m->llmnr_ipv6_tcp_fd < 0)
+ return -errno;
+
+ /* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */
+ r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->llmnr_ipv6_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = bind(m->llmnr_ipv6_tcp_fd, &sa.sa, sizeof(sa.in6));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = listen(m->llmnr_ipv6_tcp_fd, SOMAXCONN);
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = sd_event_add_io(m->event, &m->llmnr_ipv6_tcp_event_source, m->llmnr_ipv6_tcp_fd, EPOLLIN, on_llmnr_stream, m);
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ return m->llmnr_ipv6_tcp_fd;
+
+fail:
+ m->llmnr_ipv6_tcp_fd = safe_close(m->llmnr_ipv6_tcp_fd);
+ return r;
+}
+
+int manager_ifindex_is_loopback(Manager *m, int ifindex) {
+ Link *l;
+ assert(m);
+
+ if (ifindex <= 0)
+ return -EINVAL;
+
+ l = hashmap_get(m->links, INT_TO_PTR(ifindex));
+ if (l->flags & IFF_LOOPBACK)
+ return 1;
+
+ return 0;
+}
+
+int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_addr) {
+ LinkAddress *a;
+
+ assert(m);
+
+ a = manager_find_link_address(m, family, in_addr);
+ if (a)
+ return a->link->ifindex;
+
+ return 0;
+}
+
+void manager_refresh_rrs(Manager *m) {
+ Iterator i;
+ Link *l;
+
+ assert(m);
+
+ m->host_ipv4_key = dns_resource_key_unref(m->host_ipv4_key);
+ m->host_ipv6_key = dns_resource_key_unref(m->host_ipv6_key);
+
+ HASHMAP_FOREACH(l, m->links, i) {
+ link_add_rrs(l, true);
+ link_add_rrs(l, false);
+ }
+}
+
+int manager_next_hostname(Manager *m) {
+ const char *p;
+ uint64_t u;
+ char *h;
+
+ assert(m);
+
+ p = strchr(m->hostname, 0);
+ assert(p);
+
+ while (p > m->hostname) {
+ if (!strchr("0123456789", p[-1]))
+ break;
+
+ p--;
+ }
+
+ if (*p == 0 || safe_atou64(p, &u) < 0 || u <= 0)
+ u = 1;
+
+ u++;
+
+ if (asprintf(&h, "%.*s%" PRIu64, (int) (p - m->hostname), m->hostname, u) < 0)
+ return -ENOMEM;
+
+ log_info("Hostname conflict, changing published hostname from '%s' to '%s'.", m->hostname, h);
+
+ free(m->hostname);
+ m->hostname = h;
+
+ manager_refresh_rrs(m);
+
+ return 0;
+}
+
+LinkAddress* manager_find_link_address(Manager *m, int family, const union in_addr_union *in_addr) {
+ Iterator i;
+ Link *l;
+
+ assert(m);
+
+ HASHMAP_FOREACH(l, m->links, i) {
+ LinkAddress *a;
+
+ a = link_find_address(l, family, in_addr);
+ if (a)
+ return a;
+ }
+
+ return NULL;
+}
+
+int manager_our_packet(Manager *m, DnsPacket *p) {
+ assert(m);
+ assert(p);
+
+ return !!manager_find_link_address(m, p->family, &p->sender);
+}
+
+static const char* const support_table[_SUPPORT_MAX] = {
+ [SUPPORT_NO] = "no",
+ [SUPPORT_YES] = "yes",
+ [SUPPORT_RESOLVE] = "resolve",
+};
+DEFINE_STRING_TABLE_LOOKUP(support, Support);