summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTom Gundersen <teg@jklm.no>2015-12-08 17:24:09 +0100
committerTom Gundersen <teg@jklm.no>2015-12-08 17:24:09 +0100
commit73f72c61086f77b75431b1c6a068cea3fe6b9222 (patch)
tree85d0751d1764287abf73e3ec9376c8331b394004 /src
parent2af917de2d2c52808974ee62d245d114313ebae6 (diff)
parent7778dffff3d8bd7438fe19a248c16203668324c9 (diff)
Merge pull request #2122 from zonque/resolved-mdns-1
resolved: more mDNS specific bits
Diffstat (limited to 'src')
-rw-r--r--src/resolve/resolved-def.h6
-rw-r--r--src/resolve/resolved-dns-cache.c40
-rw-r--r--src/resolve/resolved-dns-cache.h3
-rw-r--r--src/resolve/resolved-dns-packet.c55
-rw-r--r--src/resolve/resolved-dns-packet.h6
-rw-r--r--src/resolve/resolved-dns-rr.h8
-rw-r--r--src/resolve/resolved-dns-scope.c56
-rw-r--r--src/resolve/resolved-dns-scope.h1
-rw-r--r--src/resolve/resolved-dns-transaction.c337
-rw-r--r--src/resolve/resolved-dns-transaction.h8
-rw-r--r--src/resolve/resolved-link.c24
-rw-r--r--src/resolve/resolved-link.h3
-rw-r--r--src/resolve/resolved-manager.c23
-rw-r--r--src/resolve/resolved-manager.h8
-rw-r--r--src/resolve/resolved-mdns.c288
-rw-r--r--src/resolve/resolved-mdns.h32
16 files changed, 795 insertions, 103 deletions
diff --git a/src/resolve/resolved-def.h b/src/resolve/resolved-def.h
index db5ee57b51..6014d345f3 100644
--- a/src/resolve/resolved-def.h
+++ b/src/resolve/resolved-def.h
@@ -24,6 +24,8 @@
#define SD_RESOLVED_DNS (UINT64_C(1) << 0)
#define SD_RESOLVED_LLMNR_IPV4 (UINT64_C(1) << 1)
#define SD_RESOLVED_LLMNR_IPV6 (UINT64_C(1) << 2)
+#define SD_RESOLVED_MDNS_IPV4 (UINT64_C(1) << 3)
+#define SD_RESOLVED_MDNS_IPV6 (UINT64_C(1) << 4)
#define SD_RESOLVED_NO_CNAME (UINT64_C(1) << 5)
#define SD_RESOLVED_NO_TXT (UINT64_C(1) << 6)
#define SD_RESOLVED_NO_ADDRESS (UINT64_C(1) << 7)
@@ -31,4 +33,6 @@
#define SD_RESOLVED_AUTHENTICATED (UINT64_C(1) << 9)
#define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6)
-#define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_LLMNR|SD_RESOLVED_DNS)
+#define SD_RESOLVED_MDNS (SD_RESOLVED_MDNS_IPV4|SD_RESOLVED_MDNS_IPV6)
+
+#define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_MDNS|SD_RESOLVED_LLMNR|SD_RESOLVED_DNS)
diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c
index bcb9994a8c..6124ff659c 100644
--- a/src/resolve/resolved-dns-cache.c
+++ b/src/resolve/resolved-dns-cache.c
@@ -459,7 +459,12 @@ int dns_cache_put(
/* Second, add in positive entries for all contained RRs */
for (i = 0; i < MIN(max_rrs, answer->n_rrs); i++) {
- r = dns_cache_put_positive(c, answer->items[i].rr, authenticated, timestamp, owner_family, owner_address);
+ DnsResourceRecord *rr = answer->items[i].rr;
+
+ if (rr->key->cache_flush)
+ dns_cache_remove(c, rr->key);
+
+ r = dns_cache_put_positive(c, rr, authenticated, timestamp, owner_family, owner_address);
if (r < 0)
goto fail;
}
@@ -726,6 +731,39 @@ int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_
return 1;
}
+int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p) {
+ unsigned ancount = 0;
+ Iterator iterator;
+ DnsCacheItem *i;
+ int r;
+
+ assert(cache);
+
+ HASHMAP_FOREACH(i, cache->by_key, iterator) {
+ DnsCacheItem *j;
+
+ LIST_FOREACH(by_key, j, i) {
+ _cleanup_free_ char *t = NULL;
+
+ if (!j->rr)
+ continue;
+
+ if (!dns_key_is_shared(j->rr->key))
+ continue;
+
+ r = dns_packet_append_rr(p, j->rr, NULL, NULL);
+ if (r < 0)
+ return r;
+
+ ancount ++;
+ }
+ }
+
+ DNS_PACKET_HEADER(p)->ancount = htobe16(ancount);
+
+ return 0;
+}
+
void dns_cache_dump(DnsCache *cache, FILE *f) {
Iterator iterator;
DnsCacheItem *i;
diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h
index 5f91164785..0f28bbe543 100644
--- a/src/resolve/resolved-dns-cache.h
+++ b/src/resolve/resolved-dns-cache.h
@@ -32,6 +32,7 @@ typedef struct DnsCache {
} DnsCache;
#include "resolved-dns-answer.h"
+#include "resolved-dns-packet.h"
#include "resolved-dns-question.h"
#include "resolved-dns-rr.h"
@@ -45,3 +46,5 @@ int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_
void dns_cache_dump(DnsCache *cache, FILE *f);
bool dns_cache_is_empty(DnsCache *cache);
+
+int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p);
diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c
index ea776f7916..9bd08eeec2 100644
--- a/src/resolve/resolved-dns-packet.c
+++ b/src/resolve/resolved-dns-packet.c
@@ -88,6 +88,16 @@ int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool
0 /* ad */,
0 /* cd */,
0 /* rcode */));
+ else if (protocol == DNS_PROTOCOL_MDNS)
+ h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */,
+ 0 /* opcode */,
+ 0 /* aa */,
+ 0 /* tc */,
+ 0 /* rd (ask for recursion) */,
+ 0 /* ra */,
+ 0 /* ad */,
+ 0 /* cd */,
+ 0 /* rcode */));
else
h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */,
0 /* opcode */,
@@ -182,6 +192,13 @@ int dns_packet_validate_reply(DnsPacket *p) {
break;
+ case DNS_PROTOCOL_MDNS:
+ /* RFC 6762, Section 18 */
+ if (DNS_PACKET_RCODE(p) != 0)
+ return -EBADMSG;
+
+ break;
+
default:
break;
}
@@ -223,6 +240,18 @@ int dns_packet_validate_query(DnsPacket *p) {
break;
+ case DNS_PROTOCOL_MDNS:
+ /* RFC 6762, Section 18 */
+ if (DNS_PACKET_AA(p) != 0 ||
+ DNS_PACKET_RD(p) != 0 ||
+ DNS_PACKET_RA(p) != 0 ||
+ DNS_PACKET_AD(p) != 0 ||
+ DNS_PACKET_CD(p) != 0 ||
+ DNS_PACKET_RCODE(p) != 0)
+ return -EBADMSG;
+
+ break;
+
default:
break;
}
@@ -1392,6 +1421,7 @@ fail:
int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) {
_cleanup_free_ char *name = NULL;
+ bool cache_flush = false;
uint16_t class, type;
DnsResourceKey *key;
size_t saved_rindex;
@@ -1414,12 +1444,23 @@ int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) {
if (r < 0)
goto fail;
+ if (p->protocol == DNS_PROTOCOL_MDNS) {
+ /* See RFC6762, Section 10.2 */
+
+ if (class & MDNS_RR_CACHE_FLUSH) {
+ class &= ~MDNS_RR_CACHE_FLUSH;
+ cache_flush = true;
+ }
+ }
+
key = dns_resource_key_new_consume(class, type, name);
if (!key) {
r = -ENOMEM;
goto fail;
}
+ key->cache_flush = cache_flush;
+
name = NULL;
*ret = key;
@@ -1778,8 +1819,16 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
break;
- case DNS_TYPE_NSEC:
- r = dns_packet_read_name(p, &rr->nsec.next_domain_name, false, NULL);
+ case DNS_TYPE_NSEC: {
+
+ /*
+ * RFC6762, section 18.14 explicly states mDNS should use name compression.
+ * This contradicts RFC3845, section 2.1.1
+ */
+
+ bool allow_compressed = p->protocol == DNS_PROTOCOL_MDNS;
+
+ r = dns_packet_read_name(p, &rr->nsec.next_domain_name, allow_compressed, NULL);
if (r < 0)
goto fail;
@@ -1792,7 +1841,7 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
* without the NSEC bit set. */
break;
-
+ }
case DNS_TYPE_NSEC3: {
uint8_t size;
diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h
index aa2823cfb9..48b3572cb4 100644
--- a/src/resolve/resolved-dns-packet.h
+++ b/src/resolve/resolved-dns-packet.h
@@ -225,6 +225,9 @@ DnsProtocol dns_protocol_from_string(const char *s) _pure_;
#define LLMNR_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 252U) })
#define LLMNR_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03 } })
+#define MDNS_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 251U) })
+#define MDNS_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xfb } })
+
static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family, bool authenticated) {
uint64_t f;
@@ -239,6 +242,9 @@ static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family,
case DNS_PROTOCOL_LLMNR:
return f|(family == AF_INET6 ? SD_RESOLVED_LLMNR_IPV6 : SD_RESOLVED_LLMNR_IPV4);
+ case DNS_PROTOCOL_MDNS:
+ return family == AF_INET6 ? SD_RESOLVED_MDNS_IPV6 : SD_RESOLVED_MDNS_IPV4;
+
default:
break;
}
diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h
index b82fa77562..5c2306ba96 100644
--- a/src/resolve/resolved-dns-rr.h
+++ b/src/resolve/resolved-dns-rr.h
@@ -45,6 +45,9 @@ enum {
#define DNSKEY_FLAG_ZONE_KEY (UINT16_C(1) << 8)
#define DNSKEY_FLAG_SEP (UINT16_C(1) << 0)
+/* mDNS RR flags */
+#define MDNS_RR_CACHE_FLUSH (UINT16_C(1) << 15)
+
/* DNSSEC algorithm identifiers, see
* http://tools.ietf.org/html/rfc4034#appendix-A.1 and
* https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */
@@ -76,6 +79,7 @@ struct DnsResourceKey {
unsigned n_ref;
uint16_t class, type;
char *_name; /* don't access directy, use DNS_RESOURCE_KEY_NAME()! */
+ bool cache_flush:1;
};
/* Creates a temporary resource key. This is only useful to quickly
@@ -246,6 +250,10 @@ int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRec
int dns_resource_key_to_string(const DnsResourceKey *key, char **ret);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceKey*, dns_resource_key_unref);
+static inline bool dns_key_is_shared(const DnsResourceKey *key) {
+ return IN_SET(key->type, DNS_TYPE_PTR);
+}
+
DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key);
DnsResourceRecord* dns_resource_record_new_full(uint16_t class, uint16_t type, const char *name);
DnsResourceRecord* dns_resource_record_ref(DnsResourceRecord *rr);
diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c
index a90692cdf4..eae903526b 100644
--- a/src/resolve/resolved-dns-scope.c
+++ b/src/resolve/resolved-dns-scope.c
@@ -30,6 +30,7 @@
#include "random-util.h"
#include "resolved-dns-scope.h"
#include "resolved-llmnr.h"
+#include "resolved-mdns.h"
#include "socket-util.h"
#include "strv.h"
@@ -59,6 +60,7 @@ int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int
LIST_PREPEND(scopes, m->dns_scopes, s);
dns_scope_llmnr_membership(s, true);
+ dns_scope_mdns_membership(s, true);
log_debug("New scope on link %s, protocol %s, family %s", l ? l->name : "*", dns_protocol_to_string(protocol), family == AF_UNSPEC ? "*" : af_to_name(family));
@@ -95,6 +97,7 @@ DnsScope* dns_scope_free(DnsScope *s) {
log_debug("Removing scope on link %s, protocol %s, family %s", s->link ? s->link->name : "*", dns_protocol_to_string(s->protocol), s->family == AF_UNSPEC ? "*" : af_to_name(s->family));
dns_scope_llmnr_membership(s, false);
+ dns_scope_mdns_membership(s, false);
dns_scope_abort_transactions(s);
while (s->query_candidates)
@@ -162,7 +165,6 @@ int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) {
union in_addr_union addr;
int ifindex = 0, r;
int family;
- uint16_t port;
uint32_t mtu;
size_t saved_size = 0;
@@ -228,7 +230,6 @@ int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) {
return -EBUSY;
family = s->family;
- port = LLMNR_PORT;
if (family == AF_INET) {
addr.in = LLMNR_MULTICAST_IPV4_ADDRESS;
@@ -241,7 +242,30 @@ int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) {
if (fd < 0)
return fd;
- r = manager_send(s->manager, fd, ifindex, family, &addr, port, p);
+ r = manager_send(s->manager, fd, ifindex, family, &addr, LLMNR_PORT, p);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case DNS_PROTOCOL_MDNS:
+ if (!ratelimit_test(&s->ratelimit))
+ return -EBUSY;
+
+ family = s->family;
+
+ if (family == AF_INET) {
+ addr.in = MDNS_MULTICAST_IPV4_ADDRESS;
+ fd = manager_mdns_ipv4_fd(s->manager);
+ } else if (family == AF_INET6) {
+ addr.in6 = MDNS_MULTICAST_IPV6_ADDRESS;
+ fd = manager_mdns_ipv6_fd(s->manager);
+ } else
+ return -EAFNOSUPPORT;
+ if (fd < 0)
+ return fd;
+
+ r = manager_send(s->manager, fd, ifindex, family, &addr, MDNS_PORT, p);
if (r < 0)
return r;
@@ -477,19 +501,15 @@ int dns_scope_good_key(DnsScope *s, DnsResourceKey *key) {
return true;
}
-int dns_scope_llmnr_membership(DnsScope *s, bool b) {
+static int dns_scope_multicast_membership(DnsScope *s, bool b, struct in_addr in, struct in6_addr in6) {
int fd;
assert(s);
-
- if (s->protocol != DNS_PROTOCOL_LLMNR)
- return 0;
-
assert(s->link);
if (s->family == AF_INET) {
struct ip_mreqn mreqn = {
- .imr_multiaddr = LLMNR_MULTICAST_IPV4_ADDRESS,
+ .imr_multiaddr = in,
.imr_ifindex = s->link->ifindex,
};
@@ -508,7 +528,7 @@ int dns_scope_llmnr_membership(DnsScope *s, bool b) {
} else if (s->family == AF_INET6) {
struct ipv6_mreq mreq = {
- .ipv6mr_multiaddr = LLMNR_MULTICAST_IPV6_ADDRESS,
+ .ipv6mr_multiaddr = in6,
.ipv6mr_interface = s->link->ifindex,
};
@@ -527,6 +547,22 @@ int dns_scope_llmnr_membership(DnsScope *s, bool b) {
return 0;
}
+int dns_scope_llmnr_membership(DnsScope *s, bool b) {
+
+ if (s->protocol != DNS_PROTOCOL_LLMNR)
+ return 0;
+
+ return dns_scope_multicast_membership(s, b, LLMNR_MULTICAST_IPV4_ADDRESS, LLMNR_MULTICAST_IPV6_ADDRESS);
+}
+
+int dns_scope_mdns_membership(DnsScope *s, bool b) {
+
+ if (s->protocol != DNS_PROTOCOL_MDNS)
+ return 0;
+
+ return dns_scope_multicast_membership(s, b, MDNS_MULTICAST_IPV4_ADDRESS, MDNS_MULTICAST_IPV6_ADDRESS);
+}
+
static int dns_scope_make_reply_packet(
DnsScope *s,
uint16_t id,
diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h
index 15d9a1fd6f..2fc2e07deb 100644
--- a/src/resolve/resolved-dns-scope.h
+++ b/src/resolve/resolved-dns-scope.h
@@ -93,6 +93,7 @@ DnsServer *dns_scope_get_dns_server(DnsScope *s);
void dns_scope_next_dns_server(DnsScope *s);
int dns_scope_llmnr_membership(DnsScope *s, bool b);
+int dns_scope_mdns_membership(DnsScope *s, bool b);
void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p);
diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c
index 1103a34c6f..90f07e6c4b 100644
--- a/src/resolve/resolved-dns-transaction.c
+++ b/src/resolve/resolved-dns-transaction.c
@@ -24,6 +24,7 @@
#include "dns-domain.h"
#include "fd-util.h"
#include "random-util.h"
+#include "resolved-dns-cache.h"
#include "resolved-dns-transaction.h"
#include "resolved-llmnr.h"
#include "string-table.h"
@@ -243,7 +244,7 @@ static int on_stream_complete(DnsStream *s, int error) {
}
if (dns_packet_validate_reply(p) <= 0) {
- log_debug("Invalid LLMNR TCP packet.");
+ log_debug("Invalid TCP reply packet.");
dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
return 0;
}
@@ -384,6 +385,18 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
break;
+ case DNS_PROTOCOL_MDNS:
+ assert(t->scope->link);
+
+ /* For mDNS we will not accept any packets from other interfaces */
+ if (p->ifindex != t->scope->link->ifindex)
+ return;
+
+ if (p->family != t->scope->family)
+ return;
+
+ break;
+
case DNS_PROTOCOL_DNS:
break;
@@ -446,6 +459,13 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
}
if (DNS_PACKET_TC(p)) {
+
+ /* Truncated packets for mDNS are not allowed. Give up immediately. */
+ if (t->scope->protocol == DNS_PROTOCOL_MDNS) {
+ dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
+ return;
+ }
+
/* Response was truncated, let's try again with good old TCP */
r = dns_transaction_open_tcp(t);
if (r == -ESRCH) {
@@ -454,7 +474,7 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
return;
}
if (r < 0) {
- /* On LLMNR, if we cannot connect to the host,
+ /* On LLMNR and mDNS, if we cannot connect to the host,
* we immediately give up */
if (t->scope->protocol == DNS_PROTOCOL_LLMNR) {
dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
@@ -481,29 +501,31 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
return;
}
- /* Only consider responses with equivalent query section to the request */
- if (p->question->n_keys != 1 || dns_resource_key_equal(p->question->keys[0], t->key) <= 0) {
- dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
- return;
- }
+ if (t->scope->protocol == DNS_PROTOCOL_DNS) {
+ /* Only consider responses with equivalent query section to the request */
+ if (p->question->n_keys != 1 || dns_resource_key_equal(p->question->keys[0], t->key) <= 0) {
+ dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
+ return;
+ }
- /* Install the answer as answer to the transaction */
- dns_answer_unref(t->answer);
- t->answer = dns_answer_ref(p->answer);
- t->answer_rcode = DNS_PACKET_RCODE(p);
- t->answer_authenticated = t->scope->dnssec_mode == DNSSEC_TRUST && DNS_PACKET_AD(p);
-
- /* According to RFC 4795, section 2.9. only the RRs from the answer section shall be cached */
- if (DNS_PACKET_SHALL_CACHE(p))
- dns_cache_put(&t->scope->cache,
- t->key,
- DNS_PACKET_RCODE(p),
- p->answer,
- DNS_PACKET_ANCOUNT(p),
- t->answer_authenticated,
- 0,
- p->family,
- &p->sender);
+ /* Install the answer as answer to the transaction */
+ dns_answer_unref(t->answer);
+ t->answer = dns_answer_ref(p->answer);
+ t->answer_rcode = DNS_PACKET_RCODE(p);
+ t->answer_authenticated = t->scope->dnssec_mode == DNSSEC_TRUST && DNS_PACKET_AD(p);
+
+ /* According to RFC 4795, section 2.9. only the RRs from the answer section shall be cached */
+ if (DNS_PACKET_SHALL_CACHE(p))
+ dns_cache_put(&t->scope->cache,
+ t->key,
+ DNS_PACKET_RCODE(p),
+ p->answer,
+ DNS_PACKET_ANCOUNT(p),
+ t->answer_authenticated,
+ 0,
+ p->family,
+ &p->sender);
+ }
if (DNS_PACKET_RCODE(p) == DNS_RCODE_SUCCESS)
dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
@@ -571,21 +593,26 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat
assert(s);
assert(t);
- /* Timeout reached? Increase the timeout for the server used */
- switch (t->scope->protocol) {
- case DNS_PROTOCOL_DNS:
- assert(t->server);
+ if (!t->initial_jitter_scheduled || t->initial_jitter_elapsed) {
+ /* Timeout reached? Increase the timeout for the server used */
+ switch (t->scope->protocol) {
+ case DNS_PROTOCOL_DNS:
+ assert(t->server);
- dns_server_packet_lost(t->server, t->current_features, usec - t->start_usec);
+ dns_server_packet_lost(t->server, t->current_features, usec - t->start_usec);
- break;
- case DNS_PROTOCOL_LLMNR:
- case DNS_PROTOCOL_MDNS:
- dns_scope_packet_lost(t->scope, usec - t->start_usec);
+ break;
+ case DNS_PROTOCOL_LLMNR:
+ case DNS_PROTOCOL_MDNS:
+ dns_scope_packet_lost(t->scope, usec - t->start_usec);
- break;
- default:
- assert_not_reached("Invalid DNS protocol.");
+ break;
+ default:
+ assert_not_reached("Invalid DNS protocol.");
+ }
+
+ if (t->initial_jitter_scheduled)
+ t->initial_jitter_elapsed = true;
}
/* ...and try again with a new server */
@@ -598,38 +625,6 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat
return 0;
}
-static int dns_transaction_make_packet(DnsTransaction *t) {
- _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
- int r;
-
- assert(t);
-
- if (t->sent)
- return 0;
-
- r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode == DNSSEC_YES);
- if (r < 0)
- return r;
-
- r = dns_scope_good_key(t->scope, t->key);
- if (r < 0)
- return r;
- if (r == 0)
- return -EDOM;
-
- r = dns_packet_append_key(p, t->key, NULL);
- if (r < 0)
- return r;
-
- DNS_PACKET_HEADER(p)->qdcount = htobe16(1);
- DNS_PACKET_HEADER(p)->id = t->id;
-
- t->sent = p;
- p = NULL;
-
- return 0;
-}
-
static usec_t transaction_get_resend_timeout(DnsTransaction *t) {
assert(t);
assert(t->scope);
@@ -639,17 +634,18 @@ static usec_t transaction_get_resend_timeout(DnsTransaction *t) {
assert(t->server);
return t->server->resend_timeout;
- case DNS_PROTOCOL_LLMNR:
case DNS_PROTOCOL_MDNS:
+ assert(t->n_attempts > 0);
+ return (1 << (t->n_attempts - 1)) * USEC_PER_SEC;
+ case DNS_PROTOCOL_LLMNR:
return t->scope->resend_timeout;
default:
assert_not_reached("Invalid DNS protocol.");
}
}
-int dns_transaction_go(DnsTransaction *t) {
+static int dns_transaction_prepare_next_attempt(DnsTransaction *t, usec_t ts) {
bool had_stream;
- usec_t ts;
int r;
assert(t);
@@ -658,11 +654,6 @@ int dns_transaction_go(DnsTransaction *t) {
dns_transaction_stop(t);
- log_debug("Excercising transaction on scope %s on %s/%s",
- dns_protocol_to_string(t->scope->protocol),
- t->scope->link ? t->scope->link->name : "*",
- t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family));
-
if (t->n_attempts >= TRANSACTION_ATTEMPTS_MAX(t->scope->protocol)) {
dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED);
return 0;
@@ -675,8 +666,6 @@ int dns_transaction_go(DnsTransaction *t) {
return 0;
}
- assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0);
-
t->n_attempts++;
t->start_usec = ts;
t->received = dns_packet_unref(t->received);
@@ -739,31 +728,203 @@ int dns_transaction_go(DnsTransaction *t) {
}
}
- if (t->scope->protocol == DNS_PROTOCOL_LLMNR && !t->initial_jitter) {
- usec_t jitter;
+ return 1;
+}
+
+static int dns_transaction_make_packet_mdns(DnsTransaction *t) {
+
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+ bool add_known_answers = false;
+ DnsTransaction *other;
+ unsigned qdcount;
+ usec_t ts;
+ int r;
+
+ assert(t);
+ assert(t->scope->protocol == DNS_PROTOCOL_MDNS);
+
+ /* Discard any previously prepared packet, so we can start over and coaleasce again */
+ t->sent = dns_packet_unref(t->sent);
+
+ r = dns_packet_new_query(&p, t->scope->protocol, 0, false);
+ if (r < 0)
+ return r;
+
+ r = dns_packet_append_key(p, t->key, NULL);
+ if (r < 0)
+ return r;
+
+ qdcount = 1;
+
+ if (dns_key_is_shared(t->key))
+ add_known_answers = true;
+
+ /*
+ * For mDNS, we want to coalesce as many open queries in pending transactions into one single
+ * query packet on the wire as possible. To achieve that, we iterate through all pending transactions
+ * in our current scope, and see whether their timing contraints allow them to be sent.
+ */
+
+ assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0);
+
+ LIST_FOREACH(transactions_by_scope, other, t->scope->transactions) {
+
+ /* Skip ourselves */
+ if (other == t)
+ continue;
+
+ if (other->state != DNS_TRANSACTION_PENDING)
+ continue;
+
+ if (other->next_attempt_after > ts)
+ continue;
+
+ if (qdcount >= UINT16_MAX)
+ break;
+
+ r = dns_packet_append_key(p, other->key, NULL);
+
+ /*
+ * If we can't stuff more questions into the packet, just give up.
+ * One of the 'other' transactions will fire later and take care of the rest.
+ */
+ if (r == -EMSGSIZE)
+ break;
+
+ if (r < 0)
+ return r;
+
+ r = dns_transaction_prepare_next_attempt(other, ts);
+ if (r <= 0)
+ continue;
+
+ ts += transaction_get_resend_timeout(other);
+
+ r = sd_event_add_time(
+ other->scope->manager->event,
+ &other->timeout_event_source,
+ clock_boottime_or_monotonic(),
+ ts, 0,
+ on_transaction_timeout, other);
+ if (r < 0)
+ return r;
+
+ other->state = DNS_TRANSACTION_PENDING;
+ other->next_attempt_after = ts;
+
+ qdcount ++;
+
+ if (dns_key_is_shared(other->key))
+ add_known_answers = true;
+ }
+
+ DNS_PACKET_HEADER(p)->qdcount = htobe16(qdcount);
+ DNS_PACKET_HEADER(p)->id = t->id;
+
+ /* Append known answer section if we're asking for any shared record */
+ if (add_known_answers) {
+ r = dns_cache_export_shared_to_packet(&t->scope->cache, p);
+ if (r < 0)
+ return r;
+ }
+
+ t->sent = p;
+ p = NULL;
+
+ return 0;
+}
+
+static int dns_transaction_make_packet(DnsTransaction *t) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+ int r;
+
+ assert(t);
+
+ if (t->scope->protocol == DNS_PROTOCOL_MDNS)
+ return dns_transaction_make_packet_mdns(t);
+
+ if (t->sent)
+ return 0;
+
+ r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode == DNSSEC_YES);
+ if (r < 0)
+ return r;
+
+ r = dns_scope_good_key(t->scope, t->key);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EDOM;
+
+ r = dns_packet_append_key(p, t->key, NULL);
+ if (r < 0)
+ return r;
+
+ DNS_PACKET_HEADER(p)->qdcount = htobe16(1);
+ DNS_PACKET_HEADER(p)->id = t->id;
+
+ t->sent = p;
+ p = NULL;
+
+ return 0;
+}
+
+int dns_transaction_go(DnsTransaction *t) {
+ usec_t ts;
+ int r;
+
+ assert(t);
+
+ assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0);
+ r = dns_transaction_prepare_next_attempt(t, ts);
+ if (r <= 0)
+ return r;
+
+ log_debug("Excercising transaction on scope %s on %s/%s",
+ dns_protocol_to_string(t->scope->protocol),
+ t->scope->link ? t->scope->link->name : "*",
+ t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family));
+
+ if (!t->initial_jitter_scheduled &&
+ (t->scope->protocol == DNS_PROTOCOL_LLMNR ||
+ t->scope->protocol == DNS_PROTOCOL_MDNS)) {
+ usec_t jitter, accuracy;
/* RFC 4795 Section 2.7 suggests all queries should be
* delayed by a random time from 0 to JITTER_INTERVAL. */
- t->initial_jitter = true;
+ t->initial_jitter_scheduled = true;
random_bytes(&jitter, sizeof(jitter));
- jitter %= LLMNR_JITTER_INTERVAL_USEC;
+
+ switch (t->scope->protocol) {
+ case DNS_PROTOCOL_LLMNR:
+ jitter %= LLMNR_JITTER_INTERVAL_USEC;
+ accuracy = LLMNR_JITTER_INTERVAL_USEC;
+ break;
+ case DNS_PROTOCOL_MDNS:
+ jitter %= MDNS_JITTER_RANGE_USEC;
+ jitter += MDNS_JITTER_MIN_USEC;
+ accuracy = MDNS_JITTER_RANGE_USEC;
+ break;
+ default:
+ assert_not_reached("bad protocol");
+ }
r = sd_event_add_time(
t->scope->manager->event,
&t->timeout_event_source,
clock_boottime_or_monotonic(),
- ts + jitter,
- LLMNR_JITTER_INTERVAL_USEC,
+ ts + jitter, accuracy,
on_transaction_timeout, t);
if (r < 0)
return r;
t->n_attempts = 0;
+ t->next_attempt_after = ts;
t->state = DNS_TRANSACTION_PENDING;
- log_debug("Delaying LLMNR transaction for " USEC_FMT "us.", jitter);
+ log_debug("Delaying %s transaction for " USEC_FMT "us.", dns_protocol_to_string(t->scope->protocol), jitter);
return 0;
}
@@ -810,16 +971,20 @@ int dns_transaction_go(DnsTransaction *t) {
return dns_transaction_go(t);
}
+ ts += transaction_get_resend_timeout(t);
+
r = sd_event_add_time(
t->scope->manager->event,
&t->timeout_event_source,
clock_boottime_or_monotonic(),
- ts + transaction_get_resend_timeout(t), 0,
+ ts, 0,
on_transaction_timeout, t);
if (r < 0)
return r;
t->state = DNS_TRANSACTION_PENDING;
+ t->next_attempt_after = ts;
+
return 1;
}
diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h
index a3058ce6e8..af08b20e44 100644
--- a/src/resolve/resolved-dns-transaction.h
+++ b/src/resolve/resolved-dns-transaction.h
@@ -62,7 +62,8 @@ struct DnsTransaction {
DnsTransactionState state;
uint16_t id;
- bool initial_jitter;
+ bool initial_jitter_scheduled;
+ bool initial_jitter_elapsed;
DnsPacket *sent, *received;
@@ -72,6 +73,7 @@ struct DnsTransaction {
bool answer_authenticated;
usec_t start_usec;
+ usec_t next_attempt_after;
sd_event_source *timeout_event_source;
unsigned n_attempts;
@@ -119,6 +121,10 @@ DnsTransactionSource dns_transaction_source_from_string(const char *s) _pure_;
/* LLMNR Jitter interval, see RFC 4795 Section 7 */
#define LLMNR_JITTER_INTERVAL_USEC (100 * USEC_PER_MSEC)
+/* mDNS Jitter interval, see RFC 6762 Section 5.2 */
+#define MDNS_JITTER_MIN_USEC (20 * USEC_PER_MSEC)
+#define MDNS_JITTER_RANGE_USEC (100 * USEC_PER_MSEC)
+
/* Maximum attempts to send DNS requests, across all DNS servers */
#define DNS_TRANSACTION_ATTEMPTS_MAX 16
diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c
index ddd9427dab..84100bd988 100644
--- a/src/resolve/resolved-link.c
+++ b/src/resolve/resolved-link.c
@@ -77,6 +77,8 @@ Link *link_free(Link *l) {
dns_scope_free(l->unicast_scope);
dns_scope_free(l->llmnr_ipv4_scope);
dns_scope_free(l->llmnr_ipv6_scope);
+ dns_scope_free(l->mdns_ipv4_scope);
+ dns_scope_free(l->mdns_ipv6_scope);
free(l);
return NULL;
@@ -118,6 +120,28 @@ static void link_allocate_scopes(Link *l) {
}
} else
l->llmnr_ipv6_scope = dns_scope_free(l->llmnr_ipv6_scope);
+
+ if (link_relevant(l, AF_INET) &&
+ l->mdns_support != SUPPORT_NO &&
+ l->manager->mdns_support != SUPPORT_NO) {
+ if (!l->mdns_ipv4_scope) {
+ r = dns_scope_new(l->manager, &l->mdns_ipv4_scope, l, DNS_PROTOCOL_MDNS, AF_INET);
+ if (r < 0)
+ log_warning_errno(r, "Failed to allocate mDNS IPv4 scope: %m");
+ }
+ } else
+ l->mdns_ipv4_scope = dns_scope_free(l->mdns_ipv4_scope);
+
+ if (link_relevant(l, AF_INET6) &&
+ l->mdns_support != SUPPORT_NO &&
+ l->manager->mdns_support != SUPPORT_NO) {
+ if (!l->mdns_ipv6_scope) {
+ r = dns_scope_new(l->manager, &l->mdns_ipv6_scope, l, DNS_PROTOCOL_MDNS, AF_INET6);
+ if (r < 0)
+ log_warning_errno(r, "Failed to allocate mDNS IPv6 scope: %m");
+ }
+ } else
+ l->mdns_ipv6_scope = dns_scope_free(l->mdns_ipv6_scope);
}
void link_add_rrs(Link *l, bool force_remove) {
diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h
index eb00015bd5..a3b406bbc2 100644
--- a/src/resolve/resolved-link.h
+++ b/src/resolve/resolved-link.h
@@ -67,10 +67,13 @@ struct Link {
unsigned n_search_domains;
Support llmnr_support;
+ Support mdns_support;
DnsScope *unicast_scope;
DnsScope *llmnr_ipv4_scope;
DnsScope *llmnr_ipv6_scope;
+ DnsScope *mdns_ipv4_scope;
+ DnsScope *mdns_ipv6_scope;
char name[IF_NAMESIZE];
uint32_t mtu;
diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c
index 5a3696ccb0..a2677f442a 100644
--- a/src/resolve/resolved-manager.c
+++ b/src/resolve/resolved-manager.c
@@ -40,6 +40,7 @@
#include "resolved-llmnr.h"
#include "resolved-manager.h"
#include "resolved-resolv-conf.h"
+#include "resolved-mdns.h"
#include "socket-util.h"
#include "string-table.h"
#include "string-util.h"
@@ -472,6 +473,7 @@ int manager_new(Manager **ret) {
m->llmnr_ipv4_udp_fd = m->llmnr_ipv6_udp_fd = -1;
m->llmnr_ipv4_tcp_fd = m->llmnr_ipv6_tcp_fd = -1;
+ m->mdns_ipv4_fd = m->mdns_ipv6_fd = -1;
m->hostname_fd = -1;
m->llmnr_support = SUPPORT_YES;
@@ -528,6 +530,10 @@ int manager_start(Manager *m) {
if (r < 0)
return r;
+ r = manager_mdns_start(m);
+ if (r < 0)
+ return r;
+
return 0;
}
@@ -559,6 +565,7 @@ Manager *manager_free(Manager *m) {
sd_event_source_unref(m->rtnl_event_source);
manager_llmnr_stop(m);
+ manager_mdns_stop(m);
sd_bus_slot_unref(m->prepare_for_sleep_slot);
sd_event_source_unref(m->bus_retry_event_source);
@@ -1024,11 +1031,25 @@ DnsScope* manager_find_scope(Manager *m, DnsPacket *p) {
if (!l)
return NULL;
- if (p->protocol == DNS_PROTOCOL_LLMNR) {
+ switch (p->protocol) {
+ case DNS_PROTOCOL_LLMNR:
if (p->family == AF_INET)
return l->llmnr_ipv4_scope;
else if (p->family == AF_INET6)
return l->llmnr_ipv6_scope;
+
+ break;
+
+ case DNS_PROTOCOL_MDNS:
+ if (p->family == AF_INET)
+ return l->mdns_ipv4_scope;
+ else if (p->family == AF_INET6)
+ return l->mdns_ipv6_scope;
+
+ break;
+
+ default:
+ break;
}
return NULL;
diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h
index 1056f23ab7..b52273403a 100644
--- a/src/resolve/resolved-manager.h
+++ b/src/resolve/resolved-manager.h
@@ -54,6 +54,7 @@ struct Manager {
sd_event *event;
Support llmnr_support;
+ Support mdns_support;
/* Network */
Hashmap *links;
@@ -102,6 +103,13 @@ struct Manager {
sd_event_source *llmnr_ipv4_tcp_event_source;
sd_event_source *llmnr_ipv6_tcp_event_source;
+ /* mDNS */
+ int mdns_ipv4_fd;
+ int mdns_ipv6_fd;
+
+ sd_event_source *mdns_ipv4_event_source;
+ sd_event_source *mdns_ipv6_event_source;
+
/* dbus */
sd_bus *bus;
sd_event_source *bus_retry_event_source;
diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c
new file mode 100644
index 0000000000..096a4b1fe5
--- /dev/null
+++ b/src/resolve/resolved-mdns.c
@@ -0,0 +1,288 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Daniel Mack
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
+#include <resolv.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "fd-util.h"
+#include "resolved-manager.h"
+#include "resolved-mdns.h"
+
+void manager_mdns_stop(Manager *m) {
+ assert(m);
+
+ m->mdns_ipv4_event_source = sd_event_source_unref(m->mdns_ipv4_event_source);
+ m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd);
+
+ m->mdns_ipv6_event_source = sd_event_source_unref(m->mdns_ipv6_event_source);
+ m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd);
+}
+
+int manager_mdns_start(Manager *m) {
+ int r;
+
+ assert(m);
+
+ if (m->mdns_support == SUPPORT_NO)
+ return 0;
+
+ r = manager_mdns_ipv4_fd(m);
+ if (r == -EADDRINUSE)
+ goto eaddrinuse;
+ if (r < 0)
+ return r;
+
+ if (socket_ipv6_is_supported()) {
+ r = manager_mdns_ipv6_fd(m);
+ if (r == -EADDRINUSE)
+ goto eaddrinuse;
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+
+eaddrinuse:
+ log_warning("There appears to be another mDNS responder running. Turning off mDNS support.");
+ m->mdns_support = SUPPORT_NO;
+ manager_mdns_stop(m);
+
+ return 0;
+}
+
+static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+ Manager *m = userdata;
+ DnsScope *scope;
+ int r;
+
+ r = manager_recv(m, fd, DNS_PROTOCOL_MDNS, &p);
+ if (r <= 0)
+ return r;
+
+ scope = manager_find_scope(m, p);
+ if (!scope) {
+ log_warning("Got mDNS UDP packet on unknown scope. Ignoring.");
+ return 0;
+ }
+
+ if (dns_packet_validate_reply(p) > 0) {
+ unsigned i;
+
+ log_debug("Got mDNS reply packet");
+
+ /*
+ * mDNS is different from regular DNS and LLMNR with regard to handling responses.
+ * While on other protocols, we can ignore every answer that doesn't match a question
+ * we broadcast earlier, RFC6762, section 18.1 recommends looking at and caching all
+ * incoming information, regardless of the DNS packet ID.
+ *
+ * Hence, extract the packet here, and try to find a transaction for answer the we got
+ * and complete it. Also store the new information in scope's cache.
+ */
+ r = dns_packet_extract(p);
+ if (r < 0) {
+ log_debug("mDNS packet extraction failed.");
+ return 0;
+ }
+
+ dns_scope_check_conflicts(scope, p);
+
+ for (i = 0; i < p->answer->n_rrs; i++) {
+ DnsResourceRecord *rr;
+ DnsTransaction *t;
+
+ rr = p->answer->items[i].rr;
+
+ t = dns_scope_find_transaction(scope, rr->key, false);
+ if (t)
+ dns_transaction_process_reply(t, p);
+ }
+
+ dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer,
+ p->answer->n_rrs, false, 0, p->family, &p->sender);
+
+ } else if (dns_packet_validate_query(p) > 0) {
+ log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p));
+
+ dns_scope_process_query(scope, NULL, p);
+ } else
+ log_debug("Invalid mDNS UDP packet.");
+
+ return 0;
+}
+
+int manager_mdns_ipv4_fd(Manager *m) {
+ union sockaddr_union sa = {
+ .in.sin_family = AF_INET,
+ .in.sin_port = htobe16(MDNS_PORT),
+ };
+ static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255;
+ int r;
+
+ assert(m);
+
+ if (m->mdns_ipv4_fd >= 0)
+ return m->mdns_ipv4_fd;
+
+ m->mdns_ipv4_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (m->mdns_ipv4_fd < 0)
+ return -errno;
+
+ r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->mdns_ipv4_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->mdns_ipv4_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->mdns_ipv4_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = bind(m->mdns_ipv4_fd, &sa.sa, sizeof(sa.in));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = sd_event_add_io(m->event, &m->mdns_ipv4_event_source, m->mdns_ipv4_fd, EPOLLIN, on_mdns_packet, m);
+ if (r < 0)
+ goto fail;
+
+ return m->mdns_ipv4_fd;
+
+fail:
+ m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd);
+ return r;
+}
+
+int manager_mdns_ipv6_fd(Manager *m) {
+ union sockaddr_union sa = {
+ .in6.sin6_family = AF_INET6,
+ .in6.sin6_port = htobe16(MDNS_PORT),
+ };
+ static const int one = 1, ttl = 255;
+ int r;
+
+ assert(m);
+
+ if (m->mdns_ipv6_fd >= 0)
+ return m->mdns_ipv6_fd;
+
+ m->mdns_ipv6_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (m->mdns_ipv6_fd < 0)
+ return -errno;
+
+ r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */
+ r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->mdns_ipv6_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = bind(m->mdns_ipv6_fd, &sa.sa, sizeof(sa.in6));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = sd_event_add_io(m->event, &m->mdns_ipv6_event_source, m->mdns_ipv6_fd, EPOLLIN, on_mdns_packet, m);
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ return m->mdns_ipv6_fd;
+
+fail:
+ m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd);
+ return r;
+}
diff --git a/src/resolve/resolved-mdns.h b/src/resolve/resolved-mdns.h
new file mode 100644
index 0000000000..8a84010615
--- /dev/null
+++ b/src/resolve/resolved-mdns.h
@@ -0,0 +1,32 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Daniel Mack
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "resolved-manager.h"
+
+#define MDNS_PORT 5353
+
+int manager_mdns_ipv4_fd(Manager *m);
+int manager_mdns_ipv6_fd(Manager *m);
+
+void manager_mdns_stop(Manager *m);
+int manager_mdns_start(Manager *m);