summaryrefslogtreecommitdiff
path: root/src/resolve
diff options
context:
space:
mode:
Diffstat (limited to 'src/resolve')
-rw-r--r--src/resolve/resolved-dns-cache.c27
-rw-r--r--src/resolve/resolved-dns-cache.h17
-rw-r--r--src/resolve/resolved-dns-packet.c211
-rw-r--r--src/resolve/resolved-dns-packet.h10
-rw-r--r--src/resolve/resolved-dns-query.c192
-rw-r--r--src/resolve/resolved-dns-query.h8
-rw-r--r--src/resolve/resolved-dns-rr.c34
-rw-r--r--src/resolve/resolved-dns-rr.h4
-rw-r--r--src/resolve/resolved-dns-scope.c213
-rw-r--r--src/resolve/resolved-dns-scope.h8
-rw-r--r--src/resolve/resolved-dns-server.h1
-rw-r--r--src/resolve/resolved-dns-stream.c380
-rw-r--r--src/resolve/resolved-dns-stream.h61
-rw-r--r--src/resolve/resolved-dns-zone.c244
-rw-r--r--src/resolve/resolved-dns-zone.h40
-rw-r--r--src/resolve/resolved-link.c142
-rw-r--r--src/resolve/resolved-link.h10
-rw-r--r--src/resolve/resolved-manager.c317
-rw-r--r--src/resolve/resolved.h26
19 files changed, 1740 insertions, 205 deletions
diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c
index 6ea5d49fc4..8c859d19b5 100644
--- a/src/resolve/resolved-dns-cache.c
+++ b/src/resolve/resolved-dns-cache.c
@@ -28,6 +28,24 @@
/* We never keep any item longer than 10min in our cache */
#define CACHE_TTL_MAX_USEC (10 * USEC_PER_MINUTE)
+typedef enum DnsCacheItemType DnsCacheItemType;
+typedef struct DnsCacheItem DnsCacheItem;
+
+enum DnsCacheItemType {
+ DNS_CACHE_POSITIVE,
+ DNS_CACHE_NODATA,
+ DNS_CACHE_NXDOMAIN,
+};
+
+struct DnsCacheItem {
+ DnsResourceKey *key;
+ DnsResourceRecord *rr;
+ usec_t until;
+ DnsCacheItemType type;
+ unsigned prioq_idx;
+ LIST_FIELDS(DnsCacheItem, by_key);
+};
+
static void dns_cache_item_free(DnsCacheItem *i) {
if (!i)
return;
@@ -157,9 +175,11 @@ static int dns_cache_item_prioq_compare_func(const void *a, const void *b) {
return 0;
}
-static int init_cache(DnsCache *c) {
+static int dns_cache_init(DnsCache *c) {
int r;
+ assert(c);
+
r = prioq_ensure_allocated(&c->by_expiry, dns_cache_item_prioq_compare_func);
if (r < 0)
return r;
@@ -258,7 +278,7 @@ static int dns_cache_put_positive(DnsCache *c, DnsResourceRecord *rr, usec_t tim
}
/* Otherwise, add the new RR */
- r = init_cache(c);
+ r = dns_cache_init(c);
if (r < 0)
return r;
@@ -294,7 +314,7 @@ static int dns_cache_put_negative(DnsCache *c, DnsResourceKey *key, int rcode, u
if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN))
return 0;
- r = init_cache(c);
+ r = dns_cache_init(c);
if (r < 0)
return r;
@@ -394,6 +414,7 @@ int dns_cache_lookup(DnsCache *c, DnsQuestion *q, int *rcode, DnsAnswer **ret) {
assert(c);
assert(q);
+ assert(rcode);
assert(ret);
if (q->n_keys <= 0) {
diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h
index 6f5bf45a38..590cf691b3 100644
--- a/src/resolve/resolved-dns-cache.h
+++ b/src/resolve/resolved-dns-cache.h
@@ -28,8 +28,6 @@
#include "time-util.h"
#include "list.h"
-typedef struct DnsCacheItem DnsCacheItem;
-
typedef struct DnsCache {
Hashmap *by_key;
Prioq *by_expiry;
@@ -39,21 +37,6 @@ typedef struct DnsCache {
#include "resolved-dns-question.h"
#include "resolved-dns-answer.h"
-typedef enum DnsCacheItemType {
- DNS_CACHE_POSITIVE,
- DNS_CACHE_NODATA,
- DNS_CACHE_NXDOMAIN,
-} DnsCacheItemType;
-
-typedef struct DnsCacheItem {
- DnsResourceKey *key;
- DnsResourceRecord *rr;
- usec_t until;
- DnsCacheItemType type;
- unsigned prioq_idx;
- LIST_FIELDS(DnsCacheItem, by_key);
-} DnsCacheItem;
-
void dns_cache_flush(DnsCache *c);
void dns_cache_prune(DnsCache *c);
diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c
index e5a4a40341..1ff56875e6 100644
--- a/src/resolve/resolved-dns-packet.c
+++ b/src/resolve/resolved-dns-packet.c
@@ -130,7 +130,7 @@ int dns_packet_validate(DnsPacket *p) {
if (p->size > DNS_PACKET_SIZE_MAX)
return -EBADMSG;
- return 0;
+ return 1;
}
int dns_packet_validate_reply(DnsPacket *p) {
@@ -142,13 +142,44 @@ int dns_packet_validate_reply(DnsPacket *p) {
if (r < 0)
return r;
- if (DNS_PACKET_QR(p) == 0)
+ if (DNS_PACKET_QR(p) != 1)
+ return 0;
+
+ if (DNS_PACKET_OPCODE(p) != 0)
return -EBADMSG;
+ return 1;
+}
+
+int dns_packet_validate_query(DnsPacket *p) {
+ int r;
+
+ assert(p);
+
+ r = dns_packet_validate(p);
+ if (r < 0)
+ return r;
+
+ if (DNS_PACKET_QR(p) != 0)
+ return 0;
+
if (DNS_PACKET_OPCODE(p) != 0)
return -EBADMSG;
- return 0;
+ if (DNS_PACKET_TC(p))
+ return -EBADMSG;
+
+ if (p->protocol == DNS_PROTOCOL_LLMNR &&
+ DNS_PACKET_QDCOUNT(p) != 1)
+ return -EBADMSG;
+
+ if (DNS_PACKET_ANCOUNT(p) > 0)
+ return -EBADMSG;
+
+ if (DNS_PACKET_NSCOUNT(p) > 0)
+ return -EBADMSG;
+
+ return 1;
}
static int dns_packet_extend(DnsPacket *p, size_t add, void **ret, size_t *start) {
@@ -216,6 +247,20 @@ static void dns_packet_truncate(DnsPacket *p, size_t sz) {
p->size = sz;
}
+int dns_packet_append_blob(DnsPacket *p, const void *d, size_t l, size_t *start) {
+ void *q;
+ int r;
+
+ assert(p);
+
+ r = dns_packet_extend(p, l, &q, start);
+ if (r < 0)
+ return r;
+
+ memcpy(q, d, l);
+ return 0;
+}
+
int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start) {
void *d;
int r;
@@ -242,7 +287,25 @@ int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start) {
return r;
((uint8_t*) d)[0] = (uint8_t) (v >> 8);
- ((uint8_t*) d)[1] = (uint8_t) (v & 255);
+ ((uint8_t*) d)[1] = (uint8_t) v;
+
+ return 0;
+}
+
+int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start) {
+ void *d;
+ int r;
+
+ assert(p);
+
+ r = dns_packet_extend(p, sizeof(uint32_t), &d, start);
+ if (r < 0)
+ return r;
+
+ ((uint8_t*) d)[0] = (uint8_t) (v >> 24);
+ ((uint8_t*) d)[1] = (uint8_t) (v >> 16);
+ ((uint8_t*) d)[2] = (uint8_t) (v >> 8);
+ ((uint8_t*) d)[3] = (uint8_t) v;
return 0;
}
@@ -387,6 +450,114 @@ fail:
return r;
}
+int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start) {
+ size_t saved_size, rdlength_offset, end, rdlength;
+ int r;
+
+ assert(p);
+ assert(rr);
+
+ saved_size = p->size;
+
+ r = dns_packet_append_key(p, rr->key, NULL);
+ if (r < 0)
+ goto fail;
+
+ r = dns_packet_append_uint32(p, rr->ttl, NULL);
+ if (r < 0)
+ goto fail;
+
+ /* Initially we write 0 here */
+ r = dns_packet_append_uint16(p, 0, &rdlength_offset);
+ if (r < 0)
+ goto fail;
+
+ switch (rr->key->type) {
+
+ case DNS_TYPE_PTR:
+ case DNS_TYPE_NS:
+ case DNS_TYPE_CNAME:
+ r = dns_packet_append_name(p, rr->ptr.name, NULL);
+ break;
+
+ case DNS_TYPE_HINFO:
+ r = dns_packet_append_string(p, rr->hinfo.cpu, NULL);
+ if (r < 0)
+ goto fail;
+
+ r = dns_packet_append_string(p, rr->hinfo.os, NULL);
+ break;
+
+ case DNS_TYPE_A:
+ r = dns_packet_append_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL);
+ break;
+
+ case DNS_TYPE_AAAA:
+ r = dns_packet_append_blob(p, &rr->aaaa.in6_addr, sizeof(struct in6_addr), NULL);
+ break;
+
+ case DNS_TYPE_SOA:
+ r = dns_packet_append_name(p, rr->soa.mname, NULL);
+ if (r < 0)
+ goto fail;
+
+ r = dns_packet_append_name(p, rr->soa.rname, NULL);
+ if (r < 0)
+ goto fail;
+
+ r = dns_packet_append_uint32(p, rr->soa.serial, NULL);
+ if (r < 0)
+ goto fail;
+
+ r = dns_packet_append_uint32(p, rr->soa.refresh, NULL);
+ if (r < 0)
+ goto fail;
+
+ r = dns_packet_append_uint32(p, rr->soa.retry, NULL);
+ if (r < 0)
+ goto fail;
+
+ r = dns_packet_append_uint32(p, rr->soa.expire, NULL);
+ if (r < 0)
+ goto fail;
+
+ r = dns_packet_append_uint32(p, rr->soa.minimum, NULL);
+ break;
+
+ case DNS_TYPE_MX:
+ case DNS_TYPE_TXT:
+ case DNS_TYPE_SRV:
+ case DNS_TYPE_DNAME:
+ case DNS_TYPE_SSHFP:
+ default:
+ r = dns_packet_append_blob(p, rr->generic.data, rr->generic.size, NULL);
+ break;
+ }
+ if (r < 0)
+ goto fail;
+
+ /* Let's calculate the actual data size and update the field */
+ rdlength = p->size - rdlength_offset - sizeof(uint16_t);
+ if (rdlength > 0xFFFF) {
+ r = ENOSPC;
+ goto fail;
+ }
+
+ end = p->size;
+ p->size = rdlength_offset;
+ r = dns_packet_append_uint16(p, rdlength, NULL);
+ if (r < 0)
+ goto fail;
+ p->size = end;
+
+ return 0;
+
+fail:
+ dns_packet_truncate(p, saved_size);
+ return r;
+}
+
+
int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start) {
assert(p);
@@ -411,6 +582,21 @@ void dns_packet_rewind(DnsPacket *p, size_t idx) {
p->rindex = idx;
}
+int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start) {
+ const void *q;
+ int r;
+
+ assert(p);
+ assert(d);
+
+ r = dns_packet_read(p, sz, &q, start);
+ if (r < 0)
+ return r;
+
+ memcpy(d, q, sz);
+ return 0;
+}
+
int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start) {
const void *d;
int r;
@@ -696,19 +882,11 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
break;
case DNS_TYPE_A:
- r = dns_packet_read(p, sizeof(struct in_addr), &d, NULL);
- if (r < 0)
- goto fail;
-
- memcpy(&rr->a.in_addr, d, sizeof(struct in_addr));
+ r = dns_packet_read_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL);
break;
case DNS_TYPE_AAAA:
- r = dns_packet_read(p, sizeof(struct in6_addr), &d, NULL);
- if (r < 0)
- goto fail;
-
- memcpy(&rr->aaaa.in6_addr, d, sizeof(struct in6_addr));
+ r = dns_packet_read_blob(p, &rr->aaaa.in6_addr, sizeof(struct in6_addr), NULL);
break;
case DNS_TYPE_SOA:
@@ -739,6 +917,11 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
r = dns_packet_read_uint32(p, &rr->soa.minimum, NULL);
break;
+ case DNS_TYPE_MX:
+ case DNS_TYPE_TXT:
+ case DNS_TYPE_SRV:
+ case DNS_TYPE_DNAME:
+ case DNS_TYPE_SSHFP:
default:
r = dns_packet_read(p, rdlength, &d, NULL);
if (r < 0)
diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h
index b8370def32..ad4a38e6e4 100644
--- a/src/resolve/resolved-dns-packet.h
+++ b/src/resolve/resolved-dns-packet.h
@@ -77,8 +77,9 @@ struct DnsPacket {
/* Packet reception meta data */
int ifindex;
- int family;
+ int family, ipproto;
union in_addr_union sender, destination;
+ uint16_t sender_port, destination_port;
uint32_t ttl;
};
@@ -131,15 +132,20 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DnsPacket*, dns_packet_unref);
int dns_packet_validate(DnsPacket *p);
int dns_packet_validate_reply(DnsPacket *p);
+int dns_packet_validate_query(DnsPacket *p);
+int dns_packet_append_blob(DnsPacket *p, const void *d, size_t sz, size_t *start);
int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start);
int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start);
+int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start);
int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start);
int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, size_t *start);
int dns_packet_append_name(DnsPacket *p, const char *name, size_t *start);
-int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start);
+int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *key, size_t *start);
+int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start);
int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start);
+int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start);
int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start);
int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start);
int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start);
diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c
index 8b4aa3bfd7..32448c5822 100644
--- a/src/resolve/resolved-dns-query.c
+++ b/src/resolve/resolved-dns-query.c
@@ -43,8 +43,7 @@ DnsQueryTransaction* dns_query_transaction_free(DnsQueryTransaction *t) {
dns_packet_unref(t->received);
dns_answer_unref(t->cached);
- sd_event_source_unref(t->tcp_event_source);
- safe_close(t->tcp_fd);
+ dns_stream_free(t->stream);
if (t->scope) {
LIST_REMOVE(transactions_by_scope, t->scope->transactions, t);
@@ -90,7 +89,6 @@ static int dns_query_transaction_new(DnsQueryTransaction **ret, DnsScope *s, Dns
if (!t)
return -ENOMEM;
- t->tcp_fd = -1;
t->question = dns_question_ref(q);
do
@@ -119,8 +117,7 @@ static void dns_query_transaction_stop(DnsQueryTransaction *t) {
assert(t);
t->timeout_event_source = sd_event_source_unref(t->timeout_event_source);
- t->tcp_event_source = sd_event_source_unref(t->tcp_event_source);
- t->tcp_fd = safe_close(t->tcp_fd);
+ t->stream = dns_stream_free(t->stream);
}
void dns_query_transaction_complete(DnsQueryTransaction *t, DnsQueryState state) {
@@ -149,132 +146,66 @@ void dns_query_transaction_complete(DnsQueryTransaction *t, DnsQueryState state)
dns_query_transaction_gc(t);
}
-static int on_tcp_ready(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
- DnsQueryTransaction *t = userdata;
- int r;
-
- assert(t);
-
- if (revents & EPOLLOUT) {
- struct iovec iov[2];
- be16_t sz;
- ssize_t ss;
-
- sz = htobe16(t->sent->size);
-
- iov[0].iov_base = &sz;
- iov[0].iov_len = sizeof(sz);
- iov[1].iov_base = DNS_PACKET_DATA(t->sent);
- iov[1].iov_len = t->sent->size;
-
- IOVEC_INCREMENT(iov, 2, t->tcp_written);
-
- ss = writev(fd, iov, 2);
- if (ss < 0) {
- if (errno != EINTR && errno != EAGAIN) {
- dns_query_transaction_complete(t, DNS_QUERY_RESOURCES);
- return -errno;
- }
- } else
- t->tcp_written += ss;
-
- /* Are we done? If so, disable the event source for EPOLLOUT */
- if (t->tcp_written >= sizeof(sz) + t->sent->size) {
- r = sd_event_source_set_io_events(s, EPOLLIN);
- if (r < 0) {
- dns_query_transaction_complete(t, DNS_QUERY_RESOURCES);
- return r;
- }
- }
- }
-
- if (revents & (EPOLLIN|EPOLLHUP|EPOLLRDHUP)) {
-
- if (t->tcp_read < sizeof(t->tcp_read_size)) {
- ssize_t ss;
-
- ss = read(fd, (uint8_t*) &t->tcp_read_size + t->tcp_read, sizeof(t->tcp_read_size) - t->tcp_read);
- if (ss < 0) {
- if (errno != EINTR && errno != EAGAIN) {
- dns_query_transaction_complete(t, DNS_QUERY_RESOURCES);
- return -errno;
- }
- } else if (ss == 0) {
- dns_query_transaction_complete(t, DNS_QUERY_RESOURCES);
- return -EIO;
- } else
- t->tcp_read += ss;
- }
-
- if (t->tcp_read >= sizeof(t->tcp_read_size)) {
-
- if (be16toh(t->tcp_read_size) < DNS_PACKET_HEADER_SIZE) {
- dns_query_transaction_complete(t, DNS_QUERY_INVALID_REPLY);
- return -EBADMSG;
- }
+static int on_stream_complete(DnsStream *s, int error) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+ DnsQueryTransaction *t;
- if (t->tcp_read < sizeof(t->tcp_read_size) + be16toh(t->tcp_read_size)) {
- ssize_t ss;
+ assert(s);
+ assert(s->transaction);
- if (!t->received) {
- r = dns_packet_new(&t->received, t->scope->protocol, be16toh(t->tcp_read_size));
- if (r < 0) {
- dns_query_transaction_complete(t, DNS_QUERY_RESOURCES);
- return r;
- }
- }
+ /* Copy the data we care about out of the stream before we
+ * destroy it. */
+ t = s->transaction;
+ p = dns_packet_ref(s->read_packet);
- ss = read(fd,
- (uint8_t*) DNS_PACKET_DATA(t->received) + t->tcp_read - sizeof(t->tcp_read_size),
- sizeof(t->tcp_read_size) + be16toh(t->tcp_read_size) - t->tcp_read);
- if (ss < 0) {
- if (errno != EINTR && errno != EAGAIN) {
- dns_query_transaction_complete(t, DNS_QUERY_RESOURCES);
- return -errno;
- }
- } else if (ss == 0) {
- dns_query_transaction_complete(t, DNS_QUERY_RESOURCES);
- return -EIO;
- } else
- t->tcp_read += ss;
- }
+ t->stream = dns_stream_free(t->stream);
- if (t->tcp_read >= sizeof(t->tcp_read_size) + be16toh(t->tcp_read_size)) {
- t->received->size = be16toh(t->tcp_read_size);
- dns_query_transaction_process_reply(t, t->received);
- return 0;
- }
- }
+ if (error != 0) {
+ dns_query_transaction_complete(t, DNS_QUERY_RESOURCES);
+ return 0;
}
+ dns_query_transaction_process_reply(t, p);
return 0;
}
static int dns_query_transaction_open_tcp(DnsQueryTransaction *t) {
+ _cleanup_close_ int fd = -1;
int r;
assert(t);
+ if (t->stream)
+ return 0;
+
if (t->scope->protocol == DNS_PROTOCOL_DNS)
- return -ENOTSUP;
+ fd = dns_scope_tcp_socket(t->scope, AF_UNSPEC, NULL, 53);
+ else if (t->scope->protocol == DNS_PROTOCOL_LLMNR) {
+ if (!t->received)
+ return -EINVAL;
- if (t->tcp_fd >= 0)
- return 0;
+ fd = dns_scope_tcp_socket(t->scope, t->received->family, &t->received->sender, t->received->sender_port);
+ } else
+ return -EAFNOSUPPORT;
- t->tcp_written = 0;
- t->tcp_read = 0;
- t->received = dns_packet_unref(t->received);
+ if (fd < 0)
+ return fd;
+
+ r = dns_stream_new(t->scope->manager, &t->stream, t->scope->protocol, fd);
+ if (r < 0)
+ return r;
- t->tcp_fd = dns_scope_tcp_socket(t->scope);
- if (t->tcp_fd < 0)
- return t->tcp_fd;
+ fd = -1;
- r = sd_event_add_io(t->scope->manager->event, &t->tcp_event_source, t->tcp_fd, EPOLLIN|EPOLLOUT, on_tcp_ready, t);
+ r = dns_stream_write_packet(t->stream, t->sent);
if (r < 0) {
- t->tcp_fd = safe_close(t->tcp_fd);
+ t->stream = dns_stream_free(t->stream);
return r;
}
+ t->received = dns_packet_unref(t->received);
+ t->stream->complete = on_stream_complete;
+
return 0;
}
@@ -289,12 +220,46 @@ void dns_query_transaction_process_reply(DnsQueryTransaction *t, DnsPacket *p) {
* should hence not attempt to access the query or transaction
* after calling this function. */
+ if (t->scope->protocol == DNS_PROTOCOL_LLMNR) {
+ assert(t->scope->link);
+
+ /* For LLMNR we will not accept any packets from other
+ * interfaces */
+
+ if (p->ifindex != t->scope->link->ifindex)
+ return;
+
+ if (p->family != t->scope->family)
+ return;
+
+ if (p->ipproto == IPPROTO_UDP) {
+ if (p->family == AF_INET && !in_addr_equal(AF_INET, &p->destination, (union in_addr_union*) &LLMNR_MULTICAST_IPV4_ADDRESS))
+ return;
+
+ if (p->family == AF_INET6 && !in_addr_equal(AF_INET6, &p->destination, (union in_addr_union*) &LLMNR_MULTICAST_IPV6_ADDRESS))
+ return;
+ }
+ }
+
+ if (t->scope->protocol == DNS_PROTOCOL_DNS) {
+
+ /* For DNS we are fine with accepting packets on any
+ * interface, but the source IP address must be one of
+ * a valid DNS server */
+
+ if (!dns_scope_good_dns_server(t->scope, p->family, &p->sender))
+ return;
+
+ if (p->sender_port != 53)
+ return;
+ }
+
if (t->received != p) {
dns_packet_unref(t->received);
t->received = dns_packet_ref(p);
}
- if (t->tcp_fd >= 0) {
+ if (p->ipproto == IPPROTO_TCP) {
if (DNS_PACKET_TC(p)) {
/* Truncated via TCP? Somebody must be fucking with us */
dns_query_transaction_complete(t, DNS_QUERY_INVALID_REPLY);
@@ -317,7 +282,14 @@ void dns_query_transaction_process_reply(DnsQueryTransaction *t, DnsPacket *p) {
return;
}
if (r < 0) {
- /* Couldn't send? Try immediately again, with a new server */
+ /* On LLMNR, if we cannot connect to the host,
+ * we immediately give up */
+ if (t->scope->protocol == DNS_PROTOCOL_LLMNR) {
+ dns_query_transaction_complete(t, DNS_QUERY_RESOURCES);
+ return;
+ }
+
+ /* On DNS, couldn't send? Try immediately again, with a new server */
dns_scope_next_dns_server(t->scope);
r = dns_query_transaction_go(t);
diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h
index 0b76564892..37f50b67c4 100644
--- a/src/resolve/resolved-dns-query.h
+++ b/src/resolve/resolved-dns-query.h
@@ -36,6 +36,7 @@ typedef struct DnsQueryTransaction DnsQueryTransaction;
#include "resolved-dns-packet.h"
#include "resolved-dns-question.h"
#include "resolved-dns-answer.h"
+#include "resolved-dns-stream.h"
typedef enum DnsQueryState {
DNS_QUERY_NULL,
@@ -65,11 +66,8 @@ struct DnsQueryTransaction {
sd_event_source *timeout_event_source;
unsigned n_attempts;
- /* TCP connection logic */
- int tcp_fd;
- sd_event_source *tcp_event_source;
- size_t tcp_written, tcp_read;
- be16_t tcp_read_size;
+ /* TCP connection logic, if we need it */
+ DnsStream *stream;
/* Queries this transaction is referenced by and that shall by
* notified about this specific transaction completing. */
diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c
index f68eb18425..5097eff083 100644
--- a/src/resolve/resolved-dns-rr.c
+++ b/src/resolve/resolved-dns-rr.c
@@ -213,6 +213,40 @@ DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr) {
return NULL;
}
+int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *hostname) {
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
+ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
+ _cleanup_free_ char *ptr = NULL;
+ int r;
+
+ assert(ret);
+ assert(address);
+ assert(hostname);
+
+ r = dns_name_reverse(family, address, &ptr);
+ if (r < 0)
+ return r;
+
+ key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, ptr);
+ if (!key)
+ return -ENOMEM;
+
+ ptr = NULL;
+
+ rr = dns_resource_record_new(key);
+ if (!rr)
+ return -ENOMEM;
+
+ rr->ptr.name = strdup(hostname);
+ if (!rr->ptr.name)
+ return -ENOMEM;
+
+ *ret = rr;
+ rr = NULL;
+
+ return 0;
+}
+
int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecord *b) {
int r;
diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h
index 418bbede65..a9d14fc22f 100644
--- a/src/resolve/resolved-dns-rr.h
+++ b/src/resolve/resolved-dns-rr.h
@@ -26,6 +26,7 @@
#include "util.h"
#include "hashmap.h"
+#include "in-addr-util.h"
typedef struct DnsResourceKey DnsResourceKey;
typedef struct DnsResourceRecord DnsResourceRecord;
@@ -49,8 +50,8 @@ enum {
DNS_TYPE_TXT = 0x10,
DNS_TYPE_AAAA = 0x1C,
DNS_TYPE_SRV = 0x21,
- DNS_TYPE_SSHFP = 0x2C,
DNS_TYPE_DNAME = 0x27,
+ DNS_TYPE_SSHFP = 0x2C,
/* Special records */
DNS_TYPE_ANY = 0xFF,
@@ -141,6 +142,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceKey*, dns_resource_key_unref);
DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key);
DnsResourceRecord* dns_resource_record_ref(DnsResourceRecord *rr);
DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr);
+int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name);
int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecord *b);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceRecord*, dns_resource_record_unref);
diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c
index 96a2ff7fae..9a636b179c 100644
--- a/src/resolve/resolved-dns-scope.c
+++ b/src/resolve/resolved-dns-scope.c
@@ -21,6 +21,7 @@
#include <netinet/tcp.h>
+#include "missing.h"
#include "strv.h"
#include "socket-util.h"
#include "af-list.h"
@@ -77,6 +78,7 @@ DnsScope* dns_scope_free(DnsScope *s) {
}
dns_cache_flush(&s->cache);
+ dns_zone_flush(&s->zone);
LIST_REMOVE(scopes, s->manager->dns_scopes, s);
strv_free(s->domains);
@@ -130,6 +132,9 @@ int dns_scope_send(DnsScope *s, DnsPacket *p) {
if (s->protocol == DNS_PROTOCOL_DNS) {
DnsServer *srv;
+ if (DNS_PACKET_QDCOUNT(p) > 1)
+ return -ENOTSUP;
+
srv = dns_scope_get_server(s);
if (!srv)
return -ESRCH;
@@ -163,12 +168,10 @@ int dns_scope_send(DnsScope *s, DnsPacket *p) {
if (family == AF_INET) {
addr.in = LLMNR_MULTICAST_IPV4_ADDRESS;
- /* fd = manager_dns_ipv4_fd(s->manager); */
fd = manager_llmnr_ipv4_udp_fd(s->manager);
} else if (family == AF_INET6) {
addr.in6 = LLMNR_MULTICAST_IPV6_ADDRESS;
fd = manager_llmnr_ipv6_udp_fd(s->manager);
- /* fd = manager_dns_ipv6_fd(s->manager); */
} else
return -EAFNOSUPPORT;
if (fd < 0)
@@ -183,39 +186,86 @@ int dns_scope_send(DnsScope *s, DnsPacket *p) {
return 1;
}
-int dns_scope_tcp_socket(DnsScope *s) {
+int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *address, uint16_t port) {
_cleanup_close_ int fd = -1;
union sockaddr_union sa = {};
socklen_t salen;
- int one, ret;
- DnsServer *srv;
- int r;
+ static const int one = 1;
+ int ret, r;
assert(s);
+ assert((family == AF_UNSPEC) == !address);
- srv = dns_scope_get_server(s);
- if (!srv)
- return -ESRCH;
-
- sa.sa.sa_family = srv->family;
- if (srv->family == AF_INET) {
- sa.in.sin_port = htobe16(53);
- sa.in.sin_addr = srv->address.in;
- salen = sizeof(sa.in);
- } else if (srv->family == AF_INET6) {
- sa.in6.sin6_port = htobe16(53);
- sa.in6.sin6_addr = srv->address.in6;
- sa.in6.sin6_scope_id = s->link ? s->link->ifindex : 0;
- salen = sizeof(sa.in6);
- } else
- return -EAFNOSUPPORT;
+ if (family == AF_UNSPEC) {
+ DnsServer *srv;
+
+ srv = dns_scope_get_server(s);
+ if (!srv)
+ return -ESRCH;
+
+ sa.sa.sa_family = srv->family;
+ if (srv->family == AF_INET) {
+ sa.in.sin_port = htobe16(port);
+ sa.in.sin_addr = srv->address.in;
+ salen = sizeof(sa.in);
+ } else if (srv->family == AF_INET6) {
+ sa.in6.sin6_port = htobe16(port);
+ sa.in6.sin6_addr = srv->address.in6;
+ sa.in6.sin6_scope_id = s->link ? s->link->ifindex : 0;
+ salen = sizeof(sa.in6);
+ } else
+ return -EAFNOSUPPORT;
+ } else {
+ sa.sa.sa_family = family;
+
+ if (family == AF_INET) {
+ sa.in.sin_port = htobe16(port);
+ sa.in.sin_addr = address->in;
+ salen = sizeof(sa.in);
+ } else if (family == AF_INET6) {
+ sa.in6.sin6_port = htobe16(port);
+ sa.in6.sin6_addr = address->in6;
+ sa.in6.sin6_scope_id = s->link ? s->link->ifindex : 0;
+ salen = sizeof(sa.in6);
+ } else
+ return -EAFNOSUPPORT;
+ }
- fd = socket(srv->family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ fd = socket(sa.sa.sa_family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
if (fd < 0)
return -errno;
- one = 1;
- setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
+ r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
+ if (r < 0)
+ return -errno;
+
+ if (s->link) {
+ uint32_t ifindex = htobe32(s->link->ifindex);
+
+ if (sa.sa.sa_family == AF_INET) {
+ r = setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex));
+ if (r < 0)
+ return -errno;
+ } else if (sa.sa.sa_family == AF_INET6) {
+ r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex));
+ if (r < 0)
+ return -errno;
+ }
+ }
+
+ if (s->protocol == DNS_PROTOCOL_LLMNR) {
+ /* RFC 4795, section 2.5 suggests the TTL to be set to 1 */
+
+ if (sa.sa.sa_family == AF_INET) {
+ r = setsockopt(fd, IPPROTO_IP, IP_TTL, &one, sizeof(one));
+ if (r < 0)
+ return -errno;
+ } else if (sa.sa.sa_family == AF_INET6) {
+ r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &one, sizeof(one));
+ if (r < 0)
+ return -errno;
+ }
+ }
r = connect(fd, &sa.sa, salen);
if (r < 0 && errno != EINPROGRESS)
@@ -223,6 +273,7 @@ int dns_scope_tcp_socket(DnsScope *s) {
ret = fd;
fd = -1;
+
return ret;
}
@@ -324,3 +375,115 @@ int dns_scope_llmnr_membership(DnsScope *s, bool b) {
return 0;
}
+
+int dns_scope_good_dns_server(DnsScope *s, int family, const union in_addr_union *address) {
+ assert(s);
+ assert(address);
+
+ if (s->protocol != DNS_PROTOCOL_DNS)
+ return 1;
+
+ if (s->link)
+ return !!link_find_dns_server(s->link, family, address);
+ else
+ return !!manager_find_dns_server(s->manager, family, address);
+}
+
+static int dns_scope_make_reply_packet(DnsScope *s, uint16_t id, int rcode, DnsQuestion *q, DnsAnswer *a, DnsPacket **ret) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+ unsigned i;
+ int r;
+
+ assert(s);
+
+ if (q->n_keys <= 0 && a->n_rrs <= 0)
+ return -EINVAL;
+
+ r = dns_packet_new(&p, s->protocol, 0);
+ if (r < 0)
+ return r;
+
+ DNS_PACKET_HEADER(p)->id = id;
+ DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS(1, 0, 0, 0, 0, 0, 0, 0, rcode));
+
+ if (q) {
+ for (i = 0; i < q->n_keys; i++) {
+ r = dns_packet_append_key(p, q->keys[i], NULL);
+ if (r < 0)
+ return r;
+ }
+
+ DNS_PACKET_HEADER(p)->qdcount = htobe16(q->n_keys);
+ }
+
+ if (a) {
+ for (i = 0; i < a->n_rrs; i++) {
+ r = dns_packet_append_rr(p, a->rrs[i], NULL);
+ if (r < 0)
+ return r;
+ }
+
+ DNS_PACKET_HEADER(p)->ancount = htobe16(a->n_rrs);
+ }
+
+ *ret = p;
+ p = NULL;
+
+ return 0;
+}
+
+void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
+ _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
+ int r, fd;
+
+ assert(s);
+ assert(p);
+
+ if (p->protocol != DNS_PROTOCOL_LLMNR)
+ return;
+
+ r = dns_packet_extract(p);
+ if (r < 0) {
+ log_debug("Failed to extract resources from incoming packet: %s", strerror(-r));
+ return;
+ }
+
+ r = dns_zone_lookup(&s->zone, p->question, &answer);
+ if (r < 0) {
+ log_debug("Failed to lookup key: %s", strerror(-r));
+ return;
+ }
+ if (r == 0)
+ return;
+
+ r = dns_scope_make_reply_packet(s, DNS_PACKET_ID(p), DNS_RCODE_SUCCESS, p->question, answer, &reply);
+ if (r < 0) {
+ log_debug("Failed to build reply packet: %s", strerror(-r));
+ return;
+ }
+
+ if (stream)
+ r = dns_stream_write_packet(stream, reply);
+ else {
+ if (p->family == AF_INET)
+ fd = manager_llmnr_ipv4_udp_fd(s->manager);
+ else if (p->family == AF_INET6)
+ fd = manager_llmnr_ipv6_udp_fd(s->manager);
+ else {
+ log_debug("Unknown protocol");
+ return;
+ }
+ if (fd < 0) {
+ log_debug("Failed to get reply socket: %s", strerror(-fd));
+ return;
+ }
+
+ r = manager_send(s->manager, fd, p->ifindex, p->family, &p->sender, p->sender_port, reply);
+ }
+
+ if (r < 0) {
+ log_debug("Failed to send reply packet: %s", strerror(-r));
+ return;
+ }
+}
diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h
index 6c93fa80be..639c4b4e86 100644
--- a/src/resolve/resolved-dns-scope.h
+++ b/src/resolve/resolved-dns-scope.h
@@ -31,6 +31,8 @@ typedef struct DnsScope DnsScope;
#include "resolved-dns-packet.h"
#include "resolved-dns-query.h"
#include "resolved-dns-cache.h"
+#include "resolved-dns-zone.h"
+#include "resolved-dns-stream.h"
typedef enum DnsScopeMatch {
DNS_SCOPE_NO,
@@ -51,6 +53,7 @@ struct DnsScope {
char **domains;
DnsCache cache;
+ DnsZone zone;
LIST_HEAD(DnsQueryTransaction, transactions);
@@ -61,12 +64,15 @@ int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol p, int family
DnsScope* dns_scope_free(DnsScope *s);
int dns_scope_send(DnsScope *s, DnsPacket *p);
-int dns_scope_tcp_socket(DnsScope *s);
+int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *address, uint16_t port);
DnsScopeMatch dns_scope_good_domain(DnsScope *s, const char *domain);
int dns_scope_good_key(DnsScope *s, DnsResourceKey *key);
+int dns_scope_good_dns_server(DnsScope *s, int family, const union in_addr_union *address);
DnsServer *dns_scope_get_server(DnsScope *s);
void dns_scope_next_dns_server(DnsScope *s);
int dns_scope_llmnr_membership(DnsScope *s, bool b);
+
+void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p);
diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h
index 45a7c4ff3b..2736032ea6 100644
--- a/src/resolve/resolved-dns-server.h
+++ b/src/resolve/resolved-dns-server.h
@@ -31,6 +31,7 @@ typedef enum DnsServerSource DnsServerSource;
#include "resolved-dns-server.h"
enum DnsServerSource {
+ DNS_SERVER_ANY,
DNS_SERVER_SYSTEM,
DNS_SERVER_LINK,
_DNS_SERVER_SOURCE_MAX
diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c
new file mode 100644
index 0000000000..24a2288428
--- /dev/null
+++ b/src/resolve/resolved-dns-stream.c
@@ -0,0 +1,380 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ 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 <netinet/tcp.h>
+
+#include "missing.h"
+#include "resolved-dns-stream.h"
+
+#define DNS_STREAM_TIMEOUT_USEC (10 * USEC_PER_SEC)
+#define DNS_STREAMS_MAX 128
+
+static void dns_stream_stop(DnsStream *s) {
+ assert(s);
+
+ s->io_event_source = sd_event_source_unref(s->io_event_source);
+ s->timeout_event_source = sd_event_source_unref(s->timeout_event_source);
+ s->fd = safe_close(s->fd);
+}
+
+static int dns_stream_update_io(DnsStream *s) {
+ int f = 0;
+
+ assert(s);
+
+ if (s->write_packet && s->n_written < sizeof(s->write_size) + s->write_packet->size)
+ f |= EPOLLOUT;
+ if (!s->read_packet || s->n_read < sizeof(s->read_size) + s->read_packet->size)
+ f |= EPOLLIN;
+
+ return sd_event_source_set_io_events(s->io_event_source, f);
+}
+
+static int stream_complete(DnsStream *s, int error) {
+ assert(s);
+
+ dns_stream_stop(s);
+
+ if (s->complete)
+ s->complete(s, error);
+ else
+ dns_stream_free(s);
+
+ return 0;
+}
+
+static int on_stream_timeout(sd_event_source *es, usec_t usec, void *userdata) {
+ DnsStream *s = userdata;
+
+ assert(s);
+
+ return stream_complete(s, ETIMEDOUT);
+}
+
+static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
+ DnsStream *s = userdata;
+ int r;
+
+ assert(s);
+
+ if ((revents & EPOLLOUT) &&
+ s->write_packet &&
+ s->n_written < sizeof(s->write_size) + s->write_packet->size) {
+
+ struct iovec iov[2];
+ ssize_t ss;
+
+ iov[0].iov_base = &s->write_size;
+ iov[0].iov_len = sizeof(s->write_size);
+ iov[1].iov_base = DNS_PACKET_DATA(s->write_packet);
+ iov[1].iov_len = s->write_packet->size;
+
+ IOVEC_INCREMENT(iov, 2, s->n_written);
+
+ ss = writev(fd, iov, 2);
+ if (ss < 0) {
+ if (errno != EINTR && errno != EAGAIN)
+ return stream_complete(s, errno);
+ } else
+ s->n_written += ss;
+
+ /* Are we done? If so, disable the event source for EPOLLOUT */
+ if (s->n_written >= sizeof(s->write_size) + s->write_packet->size) {
+ r = dns_stream_update_io(s);
+ if (r < 0)
+ return stream_complete(s, -r);
+ }
+ }
+
+ if ((revents & (EPOLLIN|EPOLLHUP|EPOLLRDHUP)) &&
+ (!s->read_packet ||
+ s->n_read < sizeof(s->read_size) + s->read_packet->size)) {
+
+ if (s->n_read < sizeof(s->read_size)) {
+ ssize_t ss;
+
+ ss = read(fd, (uint8_t*) &s->read_size + s->n_read, sizeof(s->read_size) - s->n_read);
+ if (ss < 0) {
+ if (errno != EINTR && errno != EAGAIN)
+ return stream_complete(s, errno);
+ } else if (ss == 0)
+ return stream_complete(s, ECONNRESET);
+ else
+ s->n_read += ss;
+ }
+
+ if (s->n_read >= sizeof(s->read_size)) {
+
+ if (be16toh(s->read_size) < DNS_PACKET_HEADER_SIZE)
+ return stream_complete(s, EBADMSG);
+
+ if (s->n_read < sizeof(s->read_size) + be16toh(s->read_size)) {
+ ssize_t ss;
+
+ if (!s->read_packet) {
+ r = dns_packet_new(&s->read_packet, s->protocol, be16toh(s->read_size));
+ if (r < 0)
+ return stream_complete(s, -r);
+
+ s->read_packet->size = be16toh(s->read_size);
+ s->read_packet->ipproto = IPPROTO_TCP;
+ s->read_packet->family = s->peer.sa.sa_family;
+ s->read_packet->ttl = s->ttl;
+ s->read_packet->ifindex = s->ifindex;
+
+ if (s->read_packet->family == AF_INET) {
+ s->read_packet->sender.in = s->peer.in.sin_addr;
+ s->read_packet->sender_port = be16toh(s->peer.in.sin_port);
+ s->read_packet->destination.in = s->local.in.sin_addr;
+ s->read_packet->destination_port = be16toh(s->local.in.sin_port);
+ } else {
+ assert(s->read_packet->family == AF_INET6);
+ s->read_packet->sender.in6 = s->peer.in6.sin6_addr;
+ s->read_packet->sender_port = be16toh(s->peer.in6.sin6_port);
+ s->read_packet->destination.in6 = s->local.in6.sin6_addr;
+ s->read_packet->destination_port = be16toh(s->local.in6.sin6_port);
+
+ if (s->read_packet->ifindex == 0)
+ s->read_packet->ifindex = s->peer.in6.sin6_scope_id;
+ if (s->read_packet->ifindex == 0)
+ s->read_packet->ifindex = s->local.in6.sin6_scope_id;
+ }
+ }
+
+ ss = read(fd,
+ (uint8_t*) DNS_PACKET_DATA(s->read_packet) + s->n_read - sizeof(s->read_size),
+ sizeof(s->read_size) + be16toh(s->read_size) - s->n_read);
+ if (ss < 0) {
+ if (errno != EINTR && errno != EAGAIN)
+ return stream_complete(s, errno);
+ } else if (ss == 0)
+ return stream_complete(s, ECONNRESET);
+ else
+ s->n_read += ss;
+ }
+
+ /* Are we done? If so, disable the event source for EPOLLIN */
+ if (s->n_read >= sizeof(s->read_size) + be16toh(s->read_size)) {
+ r = dns_stream_update_io(s);
+ if (r < 0)
+ return stream_complete(s, -r);
+
+ /* If there's a packet handler
+ * installed, call that. Note that
+ * this is optional... */
+ if (s->on_packet)
+ return s->on_packet(s);
+ }
+ }
+ }
+
+ if ((s->write_packet && s->n_written >= sizeof(s->write_size) + s->write_packet->size) &&
+ (s->read_packet && s->n_read >= sizeof(s->read_size) + s->read_packet->size))
+ return stream_complete(s, 0);
+
+ return 0;
+}
+
+DnsStream *dns_stream_free(DnsStream *s) {
+ if (!s)
+ return NULL;
+
+ dns_stream_stop(s);
+
+ if (s->manager) {
+ LIST_REMOVE(streams, s->manager->dns_streams, s);
+ s->manager->n_dns_streams--;
+ }
+
+ dns_packet_unref(s->write_packet);
+ dns_packet_unref(s->read_packet);
+
+ free(s);
+
+ return 0;
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_free);
+
+int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) {
+ static const int one = 1;
+ union {
+ struct cmsghdr header; /* For alignment */
+ uint8_t buffer[CMSG_SPACE(MAX(sizeof(struct in_pktinfo), sizeof(struct in6_pktinfo)))
+ + EXTRA_CMSG_SPACE /* kernel appears to require extra space */];
+ } control;
+ struct msghdr mh = {};
+ struct cmsghdr *cmsg;
+ _cleanup_(dns_stream_freep) DnsStream *s = NULL;
+ socklen_t sl;
+ int r;
+
+ assert(m);
+ assert(fd >= 0);
+
+ if (m->n_dns_streams > DNS_STREAMS_MAX)
+ return -EBUSY;
+
+ s = new0(DnsStream, 1);
+ if (!s)
+ return -ENOMEM;
+
+ s->fd = -1;
+ s->protocol = protocol;
+
+ /* Query the remote side */
+ s->peer_salen = sizeof(s->peer);
+ r = getpeername(fd, &s->peer.sa, &s->peer_salen);
+ if (r < 0)
+ return -errno;
+ if (s->peer.sa.sa_family == AF_INET6)
+ s->ifindex = s->peer.in6.sin6_scope_id;
+
+ /* Query the local side */
+ s->local_salen = sizeof(s->local);
+ r = getsockname(fd, &s->local.sa, &s->local_salen);
+ if (r < 0)
+ return -errno;
+ if (s->local.sa.sa_family == AF_INET6 && s->ifindex <= 0)
+ s->ifindex = s->local.in6.sin6_scope_id;
+
+ /* Check consistency */
+ assert(s->peer.sa.sa_family == s->local.sa.sa_family);
+ assert(IN_SET(s->peer.sa.sa_family, AF_INET, AF_INET6));
+
+ /* Query connection meta information */
+ sl = sizeof(control);
+ if (s->peer.sa.sa_family == AF_INET) {
+ r = getsockopt(fd, IPPROTO_IP, IP_PKTOPTIONS, &control, &sl);
+ if (r < 0)
+ return -errno;
+ } else {
+ assert(s->peer.sa.sa_family == AF_INET6);
+
+ r = getsockopt(fd, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, &control, &sl);
+ if (r < 0)
+ return -errno;
+ }
+
+ mh.msg_control = &control;
+ mh.msg_controllen = sl;
+ for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg)) {
+
+ if (cmsg->cmsg_level == IPPROTO_IPV6) {
+ assert(s->peer.sa.sa_family == AF_INET6);
+
+ switch (cmsg->cmsg_type) {
+
+ case IPV6_PKTINFO: {
+ struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg);
+
+ if (s->ifindex <= 0)
+ s->ifindex = i->ipi6_ifindex;
+ break;
+ }
+
+ case IPV6_HOPLIMIT:
+ s->ttl = *(int *) CMSG_DATA(cmsg);
+ break;
+ }
+
+ } else if (cmsg->cmsg_level == IPPROTO_IP) {
+ assert(s->peer.sa.sa_family == AF_INET);
+
+ switch (cmsg->cmsg_type) {
+
+ case IP_PKTINFO: {
+ struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg);
+
+ if (s->ifindex <= 0)
+ s->ifindex = i->ipi_ifindex;
+ break;
+ }
+
+ case IP_TTL:
+ s->ttl = *(int *) CMSG_DATA(cmsg);
+ break;
+ }
+ }
+ }
+
+ /* The Linux kernel sets the interface index to the loopback
+ * device if the connection came from the local host since it
+ * avoids the routing table in such a case. Let's unset the
+ * interface index in such a case. */
+ if (s->ifindex > 0 && manager_ifindex_is_loopback(m, s->ifindex) != 0)
+ s->ifindex = 0;
+
+ /* If we don't know the interface index still, we look for the
+ * first local interface with a matching address. Yuck! */
+ if (s->ifindex <= 0)
+ s->ifindex = manager_find_ifindex(m, s->local.sa.sa_family, s->local.sa.sa_family == AF_INET ? (union in_addr_union*) &s->local.in.sin_addr : (union in_addr_union*) &s->local.in6.sin6_addr);
+
+ r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
+ if (r < 0)
+ return -errno;
+
+ if (s->protocol == DNS_PROTOCOL_LLMNR && s->ifindex > 0) {
+ uint32_t ifindex = htobe32(s->ifindex);
+
+ /* Make sure all packets for this connection are sent on the same interface */
+ if (s->local.sa.sa_family == AF_INET) {
+ r = setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex));
+ if (r < 0)
+ return -errno;
+ } else if (s->local.sa.sa_family == AF_INET6) {
+ r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex));
+ if (r < 0)
+ return -errno;
+ }
+ }
+
+ r = sd_event_add_io(m->event, &s->io_event_source, fd, EPOLLIN, on_stream_io, s);
+ if (r < 0)
+ return r;
+
+ r = sd_event_add_time(m->event, &s->timeout_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + DNS_STREAM_TIMEOUT_USEC, 0, on_stream_timeout, s);
+ if (r < 0)
+ return r;
+
+ LIST_PREPEND(streams, m->dns_streams, s);
+ s->manager = m;
+ s->fd = fd;
+ m->n_dns_streams++;
+
+ *ret = s;
+ s = NULL;
+
+ return 0;
+}
+
+int dns_stream_write_packet(DnsStream *s, DnsPacket *p) {
+ assert(s);
+
+ if (s->write_packet)
+ return -EBUSY;
+
+ s->write_packet = dns_packet_ref(p);
+ s->write_size = htobe16(p->size);
+ s->n_written = 0;
+
+ return dns_stream_update_io(s);
+}
diff --git a/src/resolve/resolved-dns-stream.h b/src/resolve/resolved-dns-stream.h
new file mode 100644
index 0000000000..db456580cb
--- /dev/null
+++ b/src/resolve/resolved-dns-stream.h
@@ -0,0 +1,61 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ 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 "socket-util.h"
+
+typedef struct DnsStream DnsStream;
+
+#include "resolved.h"
+
+struct DnsStream {
+ Manager *manager;
+
+ DnsProtocol protocol;
+
+ int fd;
+ union sockaddr_union peer;
+ socklen_t peer_salen;
+ union sockaddr_union local;
+ socklen_t local_salen;
+ int ifindex;
+ uint32_t ttl;
+
+ sd_event_source *io_event_source;
+ sd_event_source *timeout_event_source;
+
+ be16_t write_size, read_size;
+ DnsPacket *write_packet, *read_packet;
+ size_t n_written, n_read;
+
+ int (*on_packet)(DnsStream *s);
+ int (*complete)(DnsStream *s, int error);
+
+ DnsQueryTransaction *transaction;
+
+ LIST_FIELDS(DnsStream, streams);
+};
+
+int dns_stream_new(Manager *m, DnsStream **s, DnsProtocol protocol, int fd);
+DnsStream *dns_stream_free(DnsStream *s);
+
+int dns_stream_write_packet(DnsStream *s, DnsPacket *p);
diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c
new file mode 100644
index 0000000000..2325ddfe7f
--- /dev/null
+++ b/src/resolve/resolved-dns-zone.c
@@ -0,0 +1,244 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ 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 "list.h"
+
+#include "resolved-dns-zone.h"
+#include "resolved-dns-domain.h"
+#include "resolved-dns-packet.h"
+
+/* Never allow more than 1K entries */
+#define ZONE_MAX 1024
+
+typedef struct DnsZoneItem DnsZoneItem;
+
+struct DnsZoneItem {
+ DnsResourceRecord *rr;
+ bool verified;
+ LIST_FIELDS(DnsZoneItem, by_key);
+ LIST_FIELDS(DnsZoneItem, by_name);
+};
+
+static void dns_zone_item_free(DnsZoneItem *i) {
+ if (!i)
+ return;
+
+ dns_resource_record_unref(i->rr);
+ free(i);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free);
+
+static void dns_zone_item_remove_and_free(DnsZone *z, DnsZoneItem *i) {
+ DnsZoneItem *first;
+
+ assert(z);
+
+ if (!i)
+ return;
+
+ first = hashmap_get(z->by_key, i->rr->key);
+ LIST_REMOVE(by_key, first, i);
+ if (first)
+ assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
+ else
+ hashmap_remove(z->by_key, i->rr->key);
+
+ first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key));
+ LIST_REMOVE(by_name, first, i);
+ if (first)
+ assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0);
+ else
+ hashmap_remove(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key));
+
+ dns_zone_item_free(i);
+}
+
+void dns_zone_flush(DnsZone *z) {
+ DnsZoneItem *i;
+
+ assert(z);
+
+ while ((i = hashmap_first(z->by_key)))
+ dns_zone_item_remove_and_free(z, i);
+
+ assert(hashmap_size(z->by_key) == 0);
+ assert(hashmap_size(z->by_name) == 0);
+
+ hashmap_free(z->by_key);
+ z->by_key = NULL;
+
+ hashmap_free(z->by_name);
+ z->by_name = NULL;
+}
+
+static DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) {
+ DnsZoneItem *i;
+
+ assert(z);
+ assert(rr);
+
+ LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key))
+ if (dns_resource_record_equal(i->rr, rr))
+ return i;
+
+ return NULL;
+}
+
+void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) {
+ DnsZoneItem *i;
+
+ assert(z);
+ assert(rr);
+
+ i = dns_zone_get(z, rr);
+ if (i)
+ dns_zone_item_remove_and_free(z, i);
+}
+
+static int dns_zone_init(DnsZone *z) {
+ int r;
+
+ assert(z);
+
+ r = hashmap_ensure_allocated(&z->by_key, dns_resource_key_hash_func, dns_resource_key_compare_func);
+ if (r < 0)
+ return r;
+
+ r = hashmap_ensure_allocated(&z->by_name, dns_name_hash_func, dns_name_compare_func);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) {
+ DnsZoneItem *first;
+ int r;
+
+ first = hashmap_get(z->by_key, i->rr->key);
+ if (first) {
+ LIST_PREPEND(by_key, first, i);
+ assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
+ } else {
+ r = hashmap_put(z->by_key, i->rr->key, i);
+ if (r < 0)
+ return r;
+ }
+
+ first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key));
+ if (first) {
+ LIST_PREPEND(by_name, first, i);
+ assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0);
+ } else {
+ r = hashmap_put(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key), i);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int dns_zone_put(DnsZone *z, DnsResourceRecord *rr) {
+ _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL;
+ DnsZoneItem *existing;
+ int r;
+
+ assert(z);
+ assert(rr);
+
+ existing = dns_zone_get(z, rr);
+ if (existing)
+ return 0;
+
+ r = dns_zone_init(z);
+ if (r < 0)
+ return r;
+
+ i = new0(DnsZoneItem, 1);
+ if (!i)
+ return -ENOMEM;
+
+ i->rr = dns_resource_record_ref(rr);
+
+ r = dns_zone_link_item(z, i);
+ if (r < 0)
+ return r;
+
+ i = NULL;
+ return 0;
+}
+
+int dns_zone_lookup(DnsZone *z, DnsQuestion *q, DnsAnswer **ret) {
+ _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
+ int r;
+ unsigned i, n = 0;
+ bool has_other_rrs = false;
+
+ assert(z);
+ assert(q);
+ assert(ret);
+
+ if (q->n_keys <= 0) {
+ *ret = NULL;
+ return 0;
+ }
+
+ for (i = 0; i < q->n_keys; i++) {
+ DnsZoneItem *j;
+
+ j = hashmap_get(z->by_key, q->keys[i]);
+ if (!j) {
+ if (hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i])))
+ has_other_rrs = true;
+
+ continue;
+ }
+
+ LIST_FOREACH(by_name, j, j)
+ n++;
+ }
+
+ if (n <= 0) {
+ *ret = NULL;
+ return has_other_rrs;
+ }
+
+ answer = dns_answer_new(n);
+ if (!answer)
+ return -ENOMEM;
+
+ for (i = 0; i < q->n_keys; i++) {
+ DnsZoneItem *j;
+
+ j = hashmap_get(z->by_key, q->keys[i]);
+ LIST_FOREACH(by_key, j, j) {
+ r = dns_answer_add(answer, j->rr);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ *ret = answer;
+ answer = NULL;
+
+ return 1;
+}
diff --git a/src/resolve/resolved-dns-zone.h b/src/resolve/resolved-dns-zone.h
new file mode 100644
index 0000000000..89d0bbe351
--- /dev/null
+++ b/src/resolve/resolved-dns-zone.h
@@ -0,0 +1,40 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ 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 "hashmap.h"
+
+typedef struct DnsZone {
+ Hashmap *by_key;
+ Hashmap *by_name;
+} DnsZone;
+
+#include "resolved-dns-rr.h"
+#include "resolved-dns-question.h"
+#include "resolved-dns-answer.h"
+
+void dns_zone_flush(DnsZone *z);
+
+int dns_zone_put(DnsZone *z, DnsResourceRecord *rr);
+void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr);
+
+int dns_zone_lookup(DnsZone *z, DnsQuestion *q, DnsAnswer **answer);
diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c
index 3c6c757722..6ac7c5be98 100644
--- a/src/resolve/resolved-link.c
+++ b/src/resolve/resolved-link.c
@@ -25,6 +25,10 @@
#include "strv.h"
#include "resolved-link.h"
+#define DEFAULT_TTL (10)
+
+static void link_address_add_rrs(LinkAddress *a);
+
int link_new(Manager *m, Link **ret, int ifindex) {
_cleanup_(link_freep) Link *l = NULL;
int r;
@@ -110,6 +114,13 @@ static void link_allocate_scopes(Link *l) {
l->llmnr_ipv6_scope = dns_scope_free(l->llmnr_ipv6_scope);
}
+static void link_add_rrs(Link *l) {
+ LinkAddress *a;
+
+ LIST_FOREACH(addresses, a, l->addresses)
+ link_address_add_rrs(a);
+}
+
int link_update_rtnl(Link *l, sd_rtnl_message *m) {
const char *n = NULL;
int r;
@@ -129,6 +140,8 @@ int link_update_rtnl(Link *l, sd_rtnl_message *m) {
}
link_allocate_scopes(l);
+ link_add_rrs(l);
+
return 0;
}
@@ -183,6 +196,7 @@ int link_update_monitor(Link *l) {
link_update_dns_servers(l);
link_allocate_scopes(l);
+ link_add_rrs(l);
return 0;
}
@@ -210,7 +224,7 @@ bool link_relevant(Link *l, int family) {
return false;
}
-LinkAddress *link_find_address(Link *l, int family, union in_addr_union *in_addr) {
+LinkAddress *link_find_address(Link *l, int family, const union in_addr_union *in_addr) {
LinkAddress *a;
assert(l);
@@ -222,7 +236,7 @@ LinkAddress *link_find_address(Link *l, int family, union in_addr_union *in_addr
return NULL;
}
-DnsServer* link_find_dns_server(Link *l, int family, union in_addr_union *in_addr) {
+DnsServer* link_find_dns_server(Link *l, int family, const union in_addr_union *in_addr) {
DnsServer *s;
assert(l);
@@ -230,7 +244,6 @@ DnsServer* link_find_dns_server(Link *l, int family, union in_addr_union *in_add
LIST_FOREACH(servers, s, l->dns_servers)
if (s->family == family && in_addr_equal(family, &s->address, in_addr))
return s;
-
return NULL;
}
@@ -265,7 +278,7 @@ void link_next_dns_server(Link *l) {
l->current_dns_server = l->dns_servers;
}
-int link_address_new(Link *l, LinkAddress **ret, int family, union in_addr_union *in_addr) {
+int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr) {
LinkAddress *a;
assert(l);
@@ -291,13 +304,130 @@ LinkAddress *link_address_free(LinkAddress *a) {
if (!a)
return NULL;
- if (a->link)
+ if (a->link) {
LIST_REMOVE(addresses, a->link->addresses, a);
+ if (a->llmnr_address_rr) {
+
+ if (a->family == AF_INET && a->link->llmnr_ipv4_scope)
+ dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_address_rr);
+ else if (a->family == AF_INET6 && a->link->llmnr_ipv6_scope)
+ dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_address_rr);
+
+ dns_resource_record_unref(a->llmnr_address_rr);
+ }
+
+ if (a->llmnr_ptr_rr) {
+ if (a->family == AF_INET && a->link->llmnr_ipv4_scope)
+ dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_ptr_rr);
+ else if (a->family == AF_INET6 && a->link->llmnr_ipv6_scope)
+ dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_ptr_rr);
+
+ dns_resource_record_unref(a->llmnr_ptr_rr);
+ }
+ }
+
free(a);
return NULL;
}
+static void link_address_add_rrs(LinkAddress *a) {
+ int r;
+
+ assert(a);
+
+ if (a->family == AF_INET && a->link->llmnr_ipv4_scope) {
+
+ if (!a->link->manager->host_ipv4_key) {
+ a->link->manager->host_ipv4_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, a->link->manager->hostname);
+ if (!a->link->manager->host_ipv4_key) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (!a->llmnr_address_rr) {
+ a->llmnr_address_rr = dns_resource_record_new(a->link->manager->host_ipv4_key);
+ if (!a->llmnr_address_rr) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ a->llmnr_address_rr->a.in_addr = a->in_addr.in;
+ a->llmnr_address_rr->ttl = DEFAULT_TTL;
+ }
+
+ if (!a->llmnr_ptr_rr) {
+ r = dns_resource_record_new_reverse(&a->llmnr_ptr_rr, a->family, &a->in_addr, a->link->manager->hostname);
+ if (r < 0)
+ goto fail;
+
+ a->llmnr_ptr_rr->ttl = DEFAULT_TTL;
+ }
+
+ if (link_address_relevant(a)) {
+ r = dns_zone_put(&a->link->llmnr_ipv4_scope->zone, a->llmnr_address_rr);
+ if (r < 0)
+ goto fail;
+
+ r = dns_zone_put(&a->link->llmnr_ipv4_scope->zone, a->llmnr_ptr_rr);
+ if (r < 0)
+ goto fail;
+ } else {
+ dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_address_rr);
+ dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_ptr_rr);
+ }
+ }
+
+ if (a->family == AF_INET6 && a->link->llmnr_ipv6_scope) {
+
+ if (!a->link->manager->host_ipv6_key) {
+ a->link->manager->host_ipv6_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, a->link->manager->hostname);
+ if (!a->link->manager->host_ipv6_key) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (!a->llmnr_address_rr) {
+ a->llmnr_address_rr = dns_resource_record_new(a->link->manager->host_ipv6_key);
+ if (!a->llmnr_address_rr) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ a->llmnr_address_rr->aaaa.in6_addr = a->in_addr.in6;
+ a->llmnr_address_rr->ttl = DEFAULT_TTL;
+ }
+
+ if (!a->llmnr_ptr_rr) {
+ r = dns_resource_record_new_reverse(&a->llmnr_ptr_rr, a->family, &a->in_addr, a->link->manager->hostname);
+ if (r < 0)
+ goto fail;
+
+ a->llmnr_ptr_rr->ttl = DEFAULT_TTL;
+ }
+
+ if (link_address_relevant(a)) {
+ r = dns_zone_put(&a->link->llmnr_ipv6_scope->zone, a->llmnr_address_rr);
+ if (r < 0)
+ goto fail;
+
+ r = dns_zone_put(&a->link->llmnr_ipv6_scope->zone, a->llmnr_ptr_rr);
+ if (r < 0)
+ goto fail;
+ } else {
+ dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_address_rr);
+ dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_ptr_rr);
+ }
+ }
+
+ return;
+
+fail:
+ log_debug("Failed to update address RRs: %s", strerror(-r));
+}
+
int link_address_update_rtnl(LinkAddress *a, sd_rtnl_message *m) {
int r;
assert(a);
@@ -310,6 +440,8 @@ int link_address_update_rtnl(LinkAddress *a, sd_rtnl_message *m) {
sd_rtnl_message_addr_get_scope(m, &a->scope);
link_allocate_scopes(a->link);
+ link_add_rrs(a->link);
+
return 0;
}
diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h
index cef0400b21..f58bd54203 100644
--- a/src/resolve/resolved-link.h
+++ b/src/resolve/resolved-link.h
@@ -32,6 +32,7 @@ typedef struct LinkAddress LinkAddress;
#include "resolved.h"
#include "resolved-dns-server.h"
#include "resolved-dns-scope.h"
+#include "resolved-dns-rr.h"
struct LinkAddress {
Link *link;
@@ -41,6 +42,9 @@ struct LinkAddress {
unsigned char flags, scope;
+ DnsResourceRecord *llmnr_address_rr;
+ DnsResourceRecord *llmnr_ptr_rr;
+
LIST_FIELDS(LinkAddress, addresses);
};
@@ -71,13 +75,13 @@ Link *link_free(Link *l);
int link_update_rtnl(Link *l, sd_rtnl_message *m);
int link_update_monitor(Link *l);
bool link_relevant(Link *l, int family);
-LinkAddress* link_find_address(Link *l, int family, union in_addr_union *in_addr);
+LinkAddress* link_find_address(Link *l, int family, const union in_addr_union *in_addr);
-DnsServer* link_find_dns_server(Link *l, int family, union in_addr_union *in_addr);
+DnsServer* link_find_dns_server(Link *l, int family, const union in_addr_union *in_addr);
DnsServer* link_get_dns_server(Link *l);
void link_next_dns_server(Link *l);
-int link_address_new(Link *l, LinkAddress **ret, int family, union in_addr_union *in_addr);
+int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr);
LinkAddress *link_address_free(LinkAddress *a);
int link_address_update_rtnl(LinkAddress *a, sd_rtnl_message *m);
bool link_address_relevant(LinkAddress *l);
diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c
index f4fa197d49..a8715bd5bf 100644
--- a/src/resolve/resolved-manager.c
+++ b/src/resolve/resolved-manager.c
@@ -390,6 +390,7 @@ int manager_new(Manager **ret) {
m->dns_ipv4_fd = m->dns_ipv6_fd = -1;
m->llmnr_ipv4_udp_fd = m->llmnr_ipv6_udp_fd = -1;
+ m->llmnr_ipv4_tcp_fd = m->llmnr_ipv6_tcp_fd = -1;
m->use_llmnr = true;
@@ -397,6 +398,10 @@ int manager_new(Manager **ret) {
if (r < 0)
return r;
+ m->hostname = gethostname_malloc();
+ if (!m->hostname)
+ return -ENOMEM;
+
r = sd_event_default(&m->event);
if (r < 0)
return r;
@@ -422,6 +427,19 @@ int manager_new(Manager **ret) {
if (r < 0)
return r;
+ r = manager_llmnr_ipv4_udp_fd(m);
+ if (r < 0)
+ return r;
+ r = manager_llmnr_ipv6_udp_fd(m);
+ if (r < 0)
+ return r;
+ r = manager_llmnr_ipv4_tcp_fd(m);
+ if (r < 0)
+ return r;
+ r = manager_llmnr_ipv6_tcp_fd(m);
+ if (r < 0)
+ return r;
+
*ret = m;
m = NULL;
@@ -461,10 +479,19 @@ Manager *manager_free(Manager *m) {
safe_close(m->llmnr_ipv4_udp_fd);
safe_close(m->llmnr_ipv6_udp_fd);
+ sd_event_source_unref(m->llmnr_ipv4_tcp_event_source);
+ sd_event_source_unref(m->llmnr_ipv6_tcp_event_source);
+ safe_close(m->llmnr_ipv4_tcp_fd);
+ safe_close(m->llmnr_ipv6_tcp_fd);
+
sd_event_source_unref(m->bus_retry_event_source);
sd_bus_unref(m->bus);
sd_event_unref(m->event);
+
+ dns_resource_key_unref(m->host_ipv4_key);
+ dns_resource_key_unref(m->host_ipv6_key);
+ free(m->hostname);
free(m);
return NULL;
@@ -545,7 +572,7 @@ int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) {
struct cmsghdr header; /* For alignment */
uint8_t buffer[CMSG_SPACE(MAX(sizeof(struct in_pktinfo), sizeof(struct in6_pktinfo)))
+ CMSG_SPACE(int) /* ttl/hoplimit */
- + 1024 /* kernel appears to require extra buffer space */];
+ + EXTRA_CMSG_SPACE /* kernel appears to require extra buffer space */];
} control;
union sockaddr_union sa;
struct msghdr mh = {};
@@ -595,11 +622,15 @@ int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) {
p->size = (size_t) l;
p->family = sa.sa.sa_family;
- if (p->family == AF_INET)
+ p->ipproto = IPPROTO_UDP;
+ if (p->family == AF_INET) {
p->sender.in = sa.in.sin_addr;
- else if (p->family == AF_INET6)
+ p->sender_port = be16toh(sa.in.sin_port);
+ } else if (p->family == AF_INET6) {
p->sender.in6 = sa.in6.sin6_addr;
- else
+ p->sender_port = be16toh(sa.in6.sin6_port);
+ p->ifindex = sa.in6.sin6_scope_id;
+ } else
return -EAFNOSUPPORT;
for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg)) {
@@ -612,7 +643,9 @@ int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) {
case IPV6_PKTINFO: {
struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg);
- p->ifindex = i->ipi6_ifindex;
+ if (p->ifindex <= 0)
+ p->ifindex = i->ipi6_ifindex;
+
p->destination.in6 = i->ipi6_addr;
break;
}
@@ -630,18 +663,32 @@ int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) {
case IP_PKTINFO: {
struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg);
- p->ifindex = i->ipi_ifindex;
+ if (p->ifindex <= 0)
+ p->ifindex = i->ipi_ifindex;
+
p->destination.in = i->ipi_addr;
break;
}
- case IP_RECVTTL:
+ case IP_TTL:
p->ttl = *(int *) CMSG_DATA(cmsg);
break;
}
}
}
+ /* The Linux kernel sets the interface index to the loopback
+ * device if the packet came from the local host since it
+ * avoids the routing table in such a case. Let's unset the
+ * interface index in such a case. */
+ if (p->ifindex > 0 && manager_ifindex_is_loopback(m, p->ifindex) != 0)
+ p->ifindex = 0;
+
+ /* If we don't know the interface index still, we look for the
+ * first local interface with a matching address. Yuck! */
+ if (p->ifindex <= 0)
+ p->ifindex = manager_find_ifindex(m, p->family, &p->destination);
+
*ret = p;
p = NULL;
@@ -658,14 +705,15 @@ static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *use
if (r <= 0)
return r;
- if (dns_packet_validate_reply(p) >= 0) {
+ if (dns_packet_validate_reply(p) > 0) {
t = hashmap_get(m->dns_query_transactions, UINT_TO_PTR(DNS_PACKET_ID(p)));
if (!t)
return 0;
dns_query_transaction_process_reply(t, p);
+
} else
- log_debug("Invalid reply packet.");
+ log_debug("Invalid DNS packet.");
return 0;
}
@@ -754,7 +802,7 @@ static int sendmsg_loop(int fd, struct msghdr *mh, int flags) {
}
}
-static int manager_ipv4_send(Manager *m, int fd, int ifindex, struct in_addr *addr, uint16_t port, DnsPacket *p) {
+static int manager_ipv4_send(Manager *m, int fd, int ifindex, const struct in_addr *addr, uint16_t port, DnsPacket *p) {
union sockaddr_union sa = {
.in.sin_family = AF_INET,
};
@@ -803,7 +851,7 @@ static int manager_ipv4_send(Manager *m, int fd, int ifindex, struct in_addr *ad
return sendmsg_loop(fd, &mh, 0);
}
-static int manager_ipv6_send(Manager *m, int fd, int ifindex, struct in6_addr *addr, uint16_t port, DnsPacket *p) {
+static int manager_ipv6_send(Manager *m, int fd, int ifindex, const struct in6_addr *addr, uint16_t port, DnsPacket *p) {
union sockaddr_union sa = {
.in6.sin6_family = AF_INET6,
};
@@ -853,7 +901,7 @@ static int manager_ipv6_send(Manager *m, int fd, int ifindex, struct in6_addr *a
return sendmsg_loop(fd, &mh, 0);
}
-int manager_send(Manager *m, int fd, int ifindex, int family, union in_addr_union *addr, uint16_t port, DnsPacket *p) {
+int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *addr, uint16_t port, DnsPacket *p) {
assert(m);
assert(fd >= 0);
assert(addr);
@@ -869,7 +917,7 @@ int manager_send(Manager *m, int fd, int ifindex, int family, union in_addr_unio
}
-DnsServer* manager_find_dns_server(Manager *m, int family, union in_addr_union *in_addr) {
+DnsServer* manager_find_dns_server(Manager *m, int family, const union in_addr_union *in_addr) {
DnsServer *s;
assert(m);
@@ -943,13 +991,30 @@ static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *u
if (r <= 0)
return r;
- if (dns_packet_validate_reply(p) >= 0) {
+ if (dns_packet_validate_reply(p) > 0) {
t = hashmap_get(m->dns_query_transactions, UINT_TO_PTR(DNS_PACKET_ID(p)));
if (!t)
return 0;
dns_query_transaction_process_reply(t, p);
- }
+
+ } else if (dns_packet_validate_query(p) > 0) {
+ Link *l;
+
+ l = hashmap_get(m->links, INT_TO_PTR(p->ifindex));
+ if (l) {
+ DnsScope *scope = NULL;
+
+ if (p->family == AF_INET)
+ scope = l->llmnr_ipv4_scope;
+ else if (p->family == AF_INET6)
+ scope = l->llmnr_ipv6_scope;
+
+ if (scope)
+ dns_scope_process_query(scope, NULL, p);
+ }
+ } else
+ log_debug("Invalid LLMNR packet.");
return 0;
}
@@ -1108,3 +1173,225 @@ fail:
m->llmnr_ipv6_udp_fd = safe_close(m->llmnr_ipv6_udp_fd);
return r;
}
+
+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;
+
+ 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;
+
+ 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) {
+ Link *l;
+ Iterator i;
+
+ assert(m);
+
+ HASHMAP_FOREACH(l, m->links, i)
+ if (link_find_address(l, family, in_addr))
+ return l->ifindex;
+
+ return 0;
+}
diff --git a/src/resolve/resolved.h b/src/resolve/resolved.h
index 35ef8bdea1..6aa0fc9e56 100644
--- a/src/resolve/resolved.h
+++ b/src/resolve/resolved.h
@@ -34,6 +34,7 @@ typedef struct Manager Manager;
#include "resolved-dns-query.h"
#include "resolved-dns-server.h"
#include "resolved-dns-scope.h"
+#include "resolved-dns-stream.h"
struct Manager {
sd_event *event;
@@ -54,6 +55,9 @@ struct Manager {
LIST_HEAD(DnsQuery, dns_queries);
unsigned n_dns_queries;
+ LIST_HEAD(DnsStream, dns_streams);
+ unsigned n_dns_streams;
+
/* Unicast dns */
int dns_ipv4_fd;
int dns_ipv6_fd;
@@ -70,15 +74,22 @@ struct Manager {
/* LLMNR */
int llmnr_ipv4_udp_fd;
int llmnr_ipv6_udp_fd;
- /* int llmnr_ipv4_tcp_fd; */
- /* int llmnr_ipv6_tcp_fd; */
+ int llmnr_ipv4_tcp_fd;
+ int llmnr_ipv6_tcp_fd;
sd_event_source *llmnr_ipv4_udp_event_source;
sd_event_source *llmnr_ipv6_udp_event_source;
+ sd_event_source *llmnr_ipv4_tcp_event_source;
+ sd_event_source *llmnr_ipv6_tcp_event_source;
/* dbus */
sd_bus *bus;
sd_event_source *bus_retry_event_source;
+
+ /* The hostname we publish on LLMNR and mDNS */
+ char *hostname;
+ DnsResourceKey *host_ipv4_key;
+ DnsResourceKey *host_ipv6_key;
};
/* Manager */
@@ -89,18 +100,23 @@ Manager* manager_free(Manager *m);
int manager_parse_config_file(Manager *m);
int manager_write_resolv_conf(Manager *m);
-DnsServer* manager_find_dns_server(Manager *m, int family, union in_addr_union *in_addr);
+DnsServer* manager_find_dns_server(Manager *m, int family, const union in_addr_union *in_addr);
DnsServer *manager_get_dns_server(Manager *m);
void manager_next_dns_server(Manager *m);
uint32_t manager_find_mtu(Manager *m);
-int manager_send(Manager *m, int fd, int ifindex, int family, union in_addr_union *addr, uint16_t port, DnsPacket *p);
+int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *addr, uint16_t port, DnsPacket *p);
int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret);
int manager_dns_ipv4_fd(Manager *m);
int manager_dns_ipv6_fd(Manager *m);
int manager_llmnr_ipv4_udp_fd(Manager *m);
int manager_llmnr_ipv6_udp_fd(Manager *m);
+int manager_llmnr_ipv4_tcp_fd(Manager *m);
+int manager_llmnr_ipv6_tcp_fd(Manager *m);
+
+int manager_ifindex_is_loopback(Manager *m, int ifindex);
+int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_addr);
int manager_connect_bus(Manager *m);
@@ -108,3 +124,5 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
const struct ConfigPerfItem* resolved_gperf_lookup(const char *key, unsigned length);
int config_parse_dnsv(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+
+#define EXTRA_CMSG_SPACE 1024