1
0
mirror of https://github.com/systemd/systemd.git synced 2024-10-27 18:55:40 +03:00

resolve: add llmnr responder side for UDP and TCP

Name defending is still missing.
This commit is contained in:
Lennart Poettering 2014-07-29 14:24:02 +02:00
parent 359017c1ae
commit 623a4c97b9
23 changed files with 1754 additions and 211 deletions

View File

@ -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

View File

@ -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) {

View File

@ -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);

View File

@ -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)

View File

@ -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);

View File

@ -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);

View File

@ -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. */

View File

@ -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;

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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

View 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);
}

View 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);

View 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;
}

View 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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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