mirror of
https://github.com/systemd/systemd-stable.git
synced 2025-02-23 21:57:46 +03:00
resolve: add llmnr responder side for UDP and TCP
Name defending is still missing.
This commit is contained in:
parent
359017c1ae
commit
623a4c97b9
@ -4754,7 +4754,11 @@ systemd_resolved_SOURCES = \
|
||||
src/resolve/resolved-dns-server.h \
|
||||
src/resolve/resolved-dns-server.c \
|
||||
src/resolve/resolved-dns-cache.h \
|
||||
src/resolve/resolved-dns-cache.c
|
||||
src/resolve/resolved-dns-cache.c \
|
||||
src/resolve/resolved-dns-zone.h \
|
||||
src/resolve/resolved-dns-zone.c \
|
||||
src/resolve/resolved-dns-stream.h \
|
||||
src/resolve/resolved-dns-stream.c
|
||||
|
||||
nodist_systemd_resolved_SOURCES = \
|
||||
src/resolve/resolved-gperf.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) {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
return -EBADMSG;
|
||||
if (DNS_PACKET_QR(p) != 1)
|
||||
return 0;
|
||||
|
||||
if (DNS_PACKET_OPCODE(p) != 0)
|
||||
return -EBADMSG;
|
||||
|
||||
return 0;
|
||||
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;
|
||||
|
||||
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)
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
static int on_stream_complete(DnsStream *s, int error) {
|
||||
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
|
||||
DnsQueryTransaction *t;
|
||||
|
||||
assert(t);
|
||||
assert(s);
|
||||
assert(s->transaction);
|
||||
|
||||
if (revents & EPOLLOUT) {
|
||||
struct iovec iov[2];
|
||||
be16_t sz;
|
||||
ssize_t ss;
|
||||
/* Copy the data we care about out of the stream before we
|
||||
* destroy it. */
|
||||
t = s->transaction;
|
||||
p = dns_packet_ref(s->read_packet);
|
||||
|
||||
sz = htobe16(t->sent->size);
|
||||
t->stream = dns_stream_free(t->stream);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (t->tcp_read < sizeof(t->tcp_read_size) + be16toh(t->tcp_read_size)) {
|
||||
ssize_t ss;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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->scope->protocol == DNS_PROTOCOL_DNS)
|
||||
return -ENOTSUP;
|
||||
|
||||
if (t->tcp_fd >= 0)
|
||||
if (t->stream)
|
||||
return 0;
|
||||
|
||||
t->tcp_written = 0;
|
||||
t->tcp_read = 0;
|
||||
t->received = dns_packet_unref(t->received);
|
||||
if (t->scope->protocol == DNS_PROTOCOL_DNS)
|
||||
fd = dns_scope_tcp_socket(t->scope, AF_UNSPEC, NULL, 53);
|
||||
else if (t->scope->protocol == DNS_PROTOCOL_LLMNR) {
|
||||
if (!t->received)
|
||||
return -EINVAL;
|
||||
|
||||
t->tcp_fd = dns_scope_tcp_socket(t->scope);
|
||||
if (t->tcp_fd < 0)
|
||||
return t->tcp_fd;
|
||||
fd = dns_scope_tcp_socket(t->scope, t->received->family, &t->received->sender, t->received->sender_port);
|
||||
} else
|
||||
return -EAFNOSUPPORT;
|
||||
|
||||
r = sd_event_add_io(t->scope->manager->event, &t->tcp_event_source, t->tcp_fd, EPOLLIN|EPOLLOUT, on_tcp_ready, t);
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
|
||||
r = dns_stream_new(t->scope->manager, &t->stream, t->scope->protocol, fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
fd = -1;
|
||||
|
||||
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);
|
||||
|
@ -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. */
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
if (family == AF_UNSPEC) {
|
||||
DnsServer *srv;
|
||||
|
||||
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;
|
||||
srv = dns_scope_get_server(s);
|
||||
if (!srv)
|
||||
return -ESRCH;
|
||||
|
||||
fd = socket(srv->family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
|
||||
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(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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
380
src/resolve/resolved-dns-stream.c
Normal file
380
src/resolve/resolved-dns-stream.c
Normal file
@ -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);
|
||||
}
|
61
src/resolve/resolved-dns-stream.h
Normal file
61
src/resolve/resolved-dns-stream.h
Normal file
@ -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);
|
244
src/resolve/resolved-dns-zone.c
Normal file
244
src/resolve/resolved-dns-zone.c
Normal file
@ -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;
|
||||
}
|
40
src/resolve/resolved-dns-zone.h
Normal file
40
src/resolve/resolved-dns-zone.h
Normal file
@ -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);
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -23,7 +23,7 @@
|
||||
|
||||
#include "in-addr-util.h"
|
||||
|
||||
int in_addr_null(int family, union in_addr_union *u) {
|
||||
int in_addr_null(int family, const union in_addr_union *u) {
|
||||
assert(u);
|
||||
|
||||
if (family == AF_INET)
|
||||
@ -40,7 +40,7 @@ int in_addr_null(int family, union in_addr_union *u) {
|
||||
}
|
||||
|
||||
|
||||
int in_addr_equal(int family, union in_addr_union *a, union in_addr_union *b) {
|
||||
int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b) {
|
||||
assert(a);
|
||||
assert(b);
|
||||
|
||||
|
@ -31,8 +31,8 @@ union in_addr_union {
|
||||
struct in6_addr in6;
|
||||
};
|
||||
|
||||
int in_addr_null(int family, union in_addr_union *u);
|
||||
int in_addr_equal(int family, union in_addr_union *a, union in_addr_union *b);
|
||||
int in_addr_null(int family, const union in_addr_union *u);
|
||||
int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b);
|
||||
int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen);
|
||||
int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen);
|
||||
int in_addr_to_string(int family, const union in_addr_union *u, char **ret);
|
||||
|
@ -499,3 +499,7 @@ static inline int setns(int fd, int nstype) {
|
||||
|
||||
#define IFLA_BRIDGE_MAX (__IFLA_BRIDGE_MAX - 1)
|
||||
#endif
|
||||
|
||||
#ifndef IPV6_UNICAST_IF
|
||||
#define IPV6_UNICAST_IF 76
|
||||
#endif
|
||||
|
Loading…
x
Reference in New Issue
Block a user