From 74b2466e14a1961bf3ac0e8a60cfaceec705bd59 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 16 Jul 2014 00:26:02 +0200 Subject: [PATCH] resolved: add a DNS client stub resolver Let's turn resolved into a something truly useful: a fully asynchronous DNS stub resolver that subscribes to network changes. (More to come: caching, LLMNR, mDNS/DNS-SD, DNSSEC, IDN, NSS module) --- .gitignore | 1 + Makefile.am | 36 +- src/resolve/resolved-bus.c | 468 +++++++++++++++++ src/resolve/resolved-dns-domain.c | 381 ++++++++++++++ src/resolve/resolved-dns-domain.h | 43 ++ src/resolve/resolved-dns-packet.c | 734 +++++++++++++++++++++++++++ src/resolve/resolved-dns-packet.h | 132 +++++ src/resolve/resolved-dns-query.c | 408 +++++++++++++++ src/resolve/resolved-dns-query.h | 97 ++++ src/resolve/resolved-dns-rr.c | 76 +++ src/resolve/resolved-dns-rr.h | 114 +++++ src/resolve/resolved-dns-scope.c | 147 ++++++ src/resolve/resolved-dns-scope.h | 69 +++ src/resolve/resolved-dns-server.c | 98 ++++ src/resolve/resolved-dns-server.h | 61 +++ src/resolve/resolved-gperf.gperf | 2 +- src/resolve/resolved-link.c | 378 ++++++++++++++ src/resolve/resolved-link.h | 84 +++ src/resolve/resolved-manager.c | 817 +++++++++++++++++++++++++----- src/resolve/resolved.c | 35 +- src/resolve/resolved.h | 73 ++- src/resolve/test-dns-domain.c | 173 +++++++ src/shared/bus-errors.h | 4 + src/shared/in-addr-util.c | 22 + src/shared/in-addr-util.h | 1 + src/shared/macro.h | 3 + src/systemd/sd-dhcp-lease.h | 2 + 27 files changed, 4289 insertions(+), 170 deletions(-) create mode 100644 src/resolve/resolved-bus.c create mode 100644 src/resolve/resolved-dns-domain.c create mode 100644 src/resolve/resolved-dns-domain.h create mode 100644 src/resolve/resolved-dns-packet.c create mode 100644 src/resolve/resolved-dns-packet.h create mode 100644 src/resolve/resolved-dns-query.c create mode 100644 src/resolve/resolved-dns-query.h create mode 100644 src/resolve/resolved-dns-rr.c create mode 100644 src/resolve/resolved-dns-rr.h create mode 100644 src/resolve/resolved-dns-scope.c create mode 100644 src/resolve/resolved-dns-scope.h create mode 100644 src/resolve/resolved-dns-server.c create mode 100644 src/resolve/resolved-dns-server.h create mode 100644 src/resolve/resolved-link.c create mode 100644 src/resolve/resolved-link.h create mode 100644 src/resolve/test-dns-domain.c diff --git a/.gitignore b/.gitignore index 31cd8f85d..9df4d58b8 100644 --- a/.gitignore +++ b/.gitignore @@ -157,6 +157,7 @@ /test-dhcp-option /test-dhcp-server /test-dhcp6-client +/test-dns-domain /test-icmp6-rs /test-ellipsize /test-engine diff --git a/Makefile.am b/Makefile.am index 934b91cd1..ee7066ec2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4536,11 +4536,22 @@ if ENABLE_RESOLVED systemd_resolved_SOURCES = \ src/resolve/resolved.h \ src/resolve/resolved.c \ - src/resolve/resolved-manager.c - -systemd_resolved_CFLAGS = \ - $(AM_CFLAGS) \ - $(KMOD_CFLAGS) + src/resolve/resolved-manager.c \ + src/resolve/resolved-bus.c \ + src/resolve/resolved-link.h \ + src/resolve/resolved-link.c \ + src/resolve/resolved-dns-domain.h \ + src/resolve/resolved-dns-domain.c \ + src/resolve/resolved-dns-packet.h \ + src/resolve/resolved-dns-packet.c \ + src/resolve/resolved-dns-query.h \ + src/resolve/resolved-dns-query.c \ + src/resolve/resolved-dns-scope.h \ + src/resolve/resolved-dns-scope.c \ + src/resolve/resolved-dns-server.h \ + src/resolve/resolved-dns-server.c \ + src/resolve/resolved-dns-rr.h \ + src/resolve/resolved-dns-rr.c nodist_systemd_resolved_SOURCES = \ src/resolve/resolved-gperf.c @@ -4579,6 +4590,21 @@ EXTRA_DIST += \ CLEANFILES += \ src/resolve/resolved.conf +tests += \ + test-dns-domain + +test_dns_domain_SOURCES = \ + src/resolve/resolved-dns-domain.h \ + src/resolve/resolved-dns-domain.c \ + src/resolve/test-dns-domain.c + +test_dns_domain_LDADD = \ + libsystemd-capability.la \ + libsystemd-network.la \ + libsystemd-label.la \ + libsystemd-internal.la \ + libsystemd-shared.la + endif # ------------------------------------------------------------------------------ diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c new file mode 100644 index 000000000..4a011efc4 --- /dev/null +++ b/src/resolve/resolved-bus.c @@ -0,0 +1,468 @@ +/*-*- 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 . +***/ + +#include "bus-errors.h" +#include "bus-util.h" + +#include "resolved.h" +#include "resolved-dns-domain.h" + +static void bus_method_resolve_hostname_complete(DnsQuery *q) { + int r; + + assert(q); + + switch(q->state) { + + case DNS_QUERY_SKIPPED: + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_NAME_SERVERS, "Not appropriate name servers or networks found"); + break; + + case DNS_QUERY_TIMEOUT: + r = sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "Query timed out"); + break; + + case DNS_QUERY_ATTEMPTS_MAX: + r = sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "All attempts to contact name servers or networks failed"); + break; + + case DNS_QUERY_FAILURE: { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + + if (q->rcode == DNS_RCODE_NXDOMAIN) + sd_bus_error_setf(&error, _BUS_ERROR_DNS "NXDOMAIN", "Hostname %s does not exist", q->request_hostname); + else { + const char *rc, *n; + char p[DECIMAL_STR_MAX(q->rcode)]; + + rc = dns_rcode_to_string(q->rcode); + if (!rc) { + sprintf(p, "%i", q->rcode); + rc = p; + } + + n = strappenda(_BUS_ERROR_DNS, rc); + + sd_bus_error_setf(&error, n, "Could not resolve hostname %s, server or network returned error %s", q->request_hostname, rc); + } + + r = sd_bus_reply_method_error(q->request, &error); + break; + } + + case DNS_QUERY_SUCCESS: { + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + unsigned i, n, added = 0; + + assert(q->packet); + + r = dns_packet_skip_question(q->packet); + if (r < 0) + goto parse_fail; + + r = sd_bus_message_new_method_return(q->request, &reply); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "(yayi)"); + if (r < 0) + goto finish; + + n = be16toh(DNS_PACKET_HEADER(q->packet)->ancount) + + be16toh(DNS_PACKET_HEADER(q->packet)->nscount) + + be16toh(DNS_PACKET_HEADER(q->packet)->arcount); + + for (i = 0; i < n; i++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + r = dns_packet_read_rr(q->packet, &rr, NULL); + if (r < 0) + goto parse_fail; + + if (rr->key.class != DNS_CLASS_IN) + continue; + + if (!(q->request_family != AF_INET6 && rr->key.type == DNS_TYPE_A) && + !(q->request_family != AF_INET && rr->key.type == DNS_TYPE_AAAA)) + continue; + + if (!dns_name_equal(rr->key.name, q->request_hostname)) + continue; + + r = sd_bus_message_open_container(reply, 'r', "yayi"); + if (r < 0) + goto finish; + + if (rr->key.type == DNS_TYPE_A) { + r = sd_bus_message_append(reply, "y", AF_INET); + if (r < 0) + goto finish; + + r = sd_bus_message_append_array(reply, 'y', &rr->a.in_addr, sizeof(struct in_addr)); + } else { + r = sd_bus_message_append(reply, "y", AF_INET6); + if (r < 0) + goto finish; + + r = sd_bus_message_append_array(reply, 'y', &rr->aaaa.in6_addr, sizeof(struct in6_addr)); + } + if (r < 0) + goto finish; + + r = sd_bus_message_append(reply, "i", q->packet->ifindex); + if (r < 0) + goto finish; + + r = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; + + added ++; + } + + if (added <= 0) + goto parse_fail; + + r = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; + + r = sd_bus_send(q->manager->bus, reply, NULL); + break; + } + + parse_fail: + case DNS_QUERY_INVALID_REPLY: + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_INVALID_REPLY, "Received invalid reply"); + break; + + case DNS_QUERY_NULL: + case DNS_QUERY_SENT: + assert_not_reached("Unexpected query state"); + } + +finish: + if (r < 0) + log_error("Failed to send bus reply: %s", strerror(-r)); + + dns_query_free(q); +} + +static int bus_method_resolve_hostname(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + const char *hostname; + uint8_t family; + DnsResourceKey keys[2]; + DnsQuery *q; + unsigned n = 0; + int r; + + assert(bus); + assert(message); + assert(m); + + r = sd_bus_message_read(message, "sy", &hostname, &family); + if (r < 0) + return r; + + if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %u", family); + + if (!hostname_is_valid(hostname)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", hostname); + + if (family != AF_INET6) { + keys[n].class = DNS_CLASS_IN; + keys[n].type = DNS_TYPE_A; + keys[n].name = (char*) hostname; + n++; + } + + if (family != AF_INET) { + keys[n].class = DNS_CLASS_IN; + keys[n].type = DNS_TYPE_AAAA; + keys[n].name = (char*) hostname; + n++; + } + + r = dns_query_new(m, &q, keys, n); + if (r < 0) + return r; + + q->request = sd_bus_message_ref(message); + q->request_family = family; + q->request_hostname = hostname; + q->complete = bus_method_resolve_hostname_complete; + + r = dns_query_start(q); + if (r < 0) { + dns_query_free(q); + return r; + } + + return 1; +} + +static void bus_method_resolve_address_complete(DnsQuery *q) { + _cleanup_free_ char *ip = NULL; + int r; + + assert(q); + + in_addr_to_string(q->request_family, &q->request_address, &ip); + + switch(q->state) { + + case DNS_QUERY_SKIPPED: + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_NAME_SERVERS, "Not appropriate name servers or networks found"); + break; + + case DNS_QUERY_TIMEOUT: + r = sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "Query timed out"); + break; + + case DNS_QUERY_ATTEMPTS_MAX: + r = sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "All attempts to contact name servers or networks failed"); + break; + + case DNS_QUERY_FAILURE: { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + + if (q->rcode == DNS_RCODE_NXDOMAIN) + sd_bus_error_setf(&error, _BUS_ERROR_DNS "NXDOMAIN", "No hostname known for address %s ", ip); + else { + const char *rc, *n; + char p[DECIMAL_STR_MAX(q->rcode)]; + + rc = dns_rcode_to_string(q->rcode); + if (!rc) { + sprintf(p, "%i", q->rcode); + rc = p; + } + + n = strappenda(_BUS_ERROR_DNS, rc); + + sd_bus_error_setf(&error, n, "Could not resolve address %s, server or network returned error %s", ip, rc); + } + + r = sd_bus_reply_method_error(q->request, &error); + break; + } + + case DNS_QUERY_SUCCESS: { + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + unsigned i, n, added = 0; + _cleanup_free_ char *reverse = NULL; + + assert(q->packet); + + r = dns_name_reverse(q->request_family, &q->request_address, &reverse); + if (r < 0) + goto finish; + + r = dns_packet_skip_question(q->packet); + if (r < 0) + goto parse_fail; + + r = sd_bus_message_new_method_return(q->request, &reply); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "s"); + if (r < 0) + goto finish; + + n = be16toh(DNS_PACKET_HEADER(q->packet)->ancount) + + be16toh(DNS_PACKET_HEADER(q->packet)->nscount) + + be16toh(DNS_PACKET_HEADER(q->packet)->arcount); + + for (i = 0; i < n; i++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + r = dns_packet_read_rr(q->packet, &rr, NULL); + if (r < 0) + goto parse_fail; + + if (rr->key.class != DNS_CLASS_IN) + continue; + if (rr->key.type != DNS_TYPE_PTR) + continue; + if (!dns_name_equal(rr->key.name, reverse)) + continue; + + r = sd_bus_message_append(reply, "s", rr->ptr.name); + if (r < 0) + goto finish; + + added ++; + } + + if (added <= 0) + goto parse_fail; + + r = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; + + r = sd_bus_send(q->manager->bus, reply, NULL); + break; + } + + parse_fail: + case DNS_QUERY_INVALID_REPLY: + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_INVALID_REPLY, "Received invalid reply"); + break; + + case DNS_QUERY_NULL: + case DNS_QUERY_SENT: + assert_not_reached("Unexpected query state"); + } + +finish: + if (r < 0) + log_error("Failed to send bus reply: %s", strerror(-r)); + + dns_query_free(q); +} + +static int bus_method_resolve_address(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(dns_resource_key_free) DnsResourceKey key = {}; + Manager *m = userdata; + uint8_t family; + const void *d; + int ifindex; + DnsQuery *q; + size_t sz; + int r; + + assert(bus); + assert(message); + assert(m); + + r = sd_bus_message_read(message, "y", &family); + if (r < 0) + return r; + + if (!IN_SET(family, AF_INET, AF_INET6)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %u", family); + + r = sd_bus_message_read_array(message, 'y', &d, &sz); + if (r < 0) + return r; + + if ((family == AF_INET && sz != sizeof(struct in_addr)) || + (family == AF_INET6 && sz != sizeof(struct in6_addr))) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid address size"); + + r = sd_bus_message_read(message, "i", &ifindex); + if (r < 0) + return r; + if (ifindex < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index"); + + key.class = DNS_CLASS_IN; + key.type = DNS_TYPE_PTR; + r = dns_name_reverse(family, d, &key.name); + if (r < 0) + return r; + + r = dns_query_new(m, &q, &key, 1); + if (r < 0) + return r; + + q->request = sd_bus_message_ref(message); + q->request_family = family; + memcpy(&q->request_address, d, sz); + q->complete = bus_method_resolve_address_complete; + + r = dns_query_start(q); + if (r < 0) { + dns_query_free(q); + return r; + } + + return 1; +} + +static const sd_bus_vtable resolve_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("ResolveHostname", "sy", "a(yayi)", bus_method_resolve_hostname, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ResolveAddress", "yayi", "as", bus_method_resolve_address, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END, +}; + +static int on_bus_retry(sd_event_source *s, usec_t usec, void *userdata) { + Manager *m = userdata; + + assert(s); + assert(m); + + m->bus_retry_event_source = sd_event_source_unref(m->bus_retry_event_source); + + manager_connect_bus(m); + return 0; +} + +int manager_connect_bus(Manager *m) { + int r; + + assert(m); + + if (m->bus) + return 0; + + r = sd_bus_default_system(&m->bus); + if (r < 0) { + /* We failed to connect? Yuck, we must be in early + * boot. Let's try in 5s again. As soon as we have + * kdbus we can stop doing this... */ + + log_debug("Failed to connect to bus, trying again in 5s: %s", strerror(-r)); + + r = sd_event_add_time(m->event, &m->bus_retry_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 5*USEC_PER_SEC, 0, on_bus_retry, m); + if (r < 0) { + log_error("Failed to install bus reconnect time event: %s", strerror(-r)); + return r; + } + + return 0; + } + + r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/resolve1", "org.freedesktop.resolve1", resolve_vtable, m); + if (r < 0) { + log_error("Failed to register object: %s", strerror(-r)); + return r; + } + + r = sd_bus_request_name(m->bus, "org.freedesktop.resolve1", 0); + if (r < 0) { + log_error("Failed to register name: %s", strerror(-r)); + return r; + } + + r = sd_bus_attach_event(m->bus, m->event, 0); + if (r < 0) { + log_error("Failed to attach bus to event loop: %s", strerror(-r)); + return r; + } + + return 0; +} diff --git a/src/resolve/resolved-dns-domain.c b/src/resolve/resolved-dns-domain.c new file mode 100644 index 000000000..94ffc0fac --- /dev/null +++ b/src/resolve/resolved-dns-domain.c @@ -0,0 +1,381 @@ +/*-*- 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 . + ***/ + +#include "resolved-dns-domain.h" + +int dns_label_unescape(const char **name, char *dest, size_t sz) { + const char *n; + char *d; + int r = 0; + + assert(name); + assert(*name); + assert(dest); + + n = *name; + d = dest; + + for (;;) { + if (*n == '.') { + n++; + break; + } + + if (*n == 0) + break; + + if (sz <= 0) + return -ENOSPC; + + if (*n == '\\') { + /* Escaped character */ + + n++; + + if (*n == 0) + /* Ending NUL */ + return -EINVAL; + + else if (*n == '\\' || *n == '.') { + /* Escaped backslash or dot */ + *(d++) = *(n++); + sz--; + r++; + + } else if (n[0] >= '0' && n[0] <= '9') { + unsigned k; + + /* Escaped literal ASCII character */ + + if (!(n[1] >= '0' && n[1] <= '9') || + !(n[2] >= '0' && n[2] <= '9')) + return -EINVAL; + + k = ((unsigned) (n[0] - '0') * 100) + + ((unsigned) (n[1] - '0') * 10) + + ((unsigned) (n[2] - '0')); + + /* Don't allow CC characters or anything that doesn't fit in 8bit */ + if (k < ' ' || k > 255 || k == 127) + return -EINVAL; + + *(d++) = (char) k; + sz--; + r++; + + n += 3; + } else + return -EINVAL; + + } else if (*n >= ' ' && *n != 127) { + + /* Normal character */ + *(d++) = *(n++); + sz--; + r++; + } else + return -EINVAL; + } + + /* Empty label that is not at the end? */ + if (r == 0 && *n) + return -EINVAL; + + if (sz >= 1) + *d = 0; + + *name = n; + return r; +} + +int dns_label_escape(const char *p, size_t l, char **ret) { + _cleanup_free_ char *s = NULL; + char *q; + int r; + + assert(p); + assert(ret); + + s = malloc(l * 4 + 1); + if (!s) + return -ENOMEM; + + q = s; + while (l > 0) { + + if (*p == '.' || *p == '\\') { + + /* Dot or backslash */ + *(q++) = '\\'; + *(q++) = *p; + + } else if (*p == '_' || + *p == '-' || + (*p >= '0' && *p <= '9') || + (*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z')) { + + /* Proper character */ + *(q++) = *p; + } else if (*p >= ' ' && *p != 127) { + + /* Everything else */ + *(q++) = '\\'; + *(q++) = '0' + (char) ((unsigned) *p / 100); + *(q++) = '0' + (char) (((unsigned) *p / 10) % 10); + *(q++) = '0' + (char) ((unsigned) *p % 10); + + } else + return -EINVAL; + + p++; + l--; + } + + *q = 0; + *ret = s; + r = q - s; + s = NULL; + + return r; +} + +int dns_name_normalize(const char *s, char **_ret) { + _cleanup_free_ char *ret = NULL; + size_t n = 0, allocated = 0; + const char *p = s; + bool first = true; + int r; + + assert(s); + assert(_ret); + + for (;;) { + _cleanup_free_ char *t = NULL; + char label[DNS_LABEL_MAX]; + + r = dns_label_unescape(&p, label, sizeof(label)); + if (r < 0) + return r; + if (r == 0) { + if (*p != 0) + return -EINVAL; + break; + } + + r = dns_label_escape(label, r, &t); + if (r < 0) + return r; + + if (!GREEDY_REALLOC(ret, allocated, n + !first + strlen(t) + 1)) + return -ENOMEM; + + if (!first) + ret[n++] = '.'; + else + first = false; + + memcpy(ret + n, t, r); + n += r; + } + + if (!GREEDY_REALLOC(ret, allocated, n + 1)) + return -ENOMEM; + + ret[n] = 0; + *_ret = ret; + ret = NULL; + + return 0; +} + +unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_SIZE]) { + const char *p = s; + unsigned long ul = 0; + int r; + + assert(p); + + while (*p) { + char label[DNS_LABEL_MAX+1]; + + r = dns_label_unescape(&p, label, sizeof(label)); + if (r < 0) + break; + + label[r] = 0; + ascii_strlower(label); + + ul = hash_key[0] * ul + ul + string_hash_func(label, hash_key); + } + + return ul; +} + +int dns_name_compare_func(const void *a, const void *b) { + const char *x = a, *y = b; + int r, q; + + assert(a); + assert(b); + + for (;;) { + char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; + + if (*x == 0 && *y == 0) + return 0; + + r = dns_label_unescape(&x, la, sizeof(la)); + q = dns_label_unescape(&y, lb, sizeof(lb)); + if (r < 0 || q < 0) + return r - q; + + la[r] = lb[q] = 0; + r = strcasecmp(la, lb); + if (r != 0) + return r; + } +} + +int dns_name_equal(const char *x, const char *y) { + int r, q; + + assert(x); + assert(y); + + for (;;) { + char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; + + if (*x == 0 && *y == 0) + return true; + + r = dns_label_unescape(&x, la, sizeof(la)); + if (r < 0) + return r; + + q = dns_label_unescape(&y, lb, sizeof(lb)); + if (q < 0) + return q; + + la[r] = lb[q] = 0; + if (strcasecmp(la, lb)) + return false; + } +} + +int dns_name_endswith(const char *name, const char *suffix) { + const char *n, *s, *saved_n = NULL; + int r, q; + + assert(name); + assert(suffix); + + n = name; + s = suffix; + + for (;;) { + char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1]; + + r = dns_label_unescape(&n, ln, sizeof(ln)); + if (r < 0) + return r; + + if (!saved_n) + saved_n = n; + + q = dns_label_unescape(&s, ls, sizeof(ls)); + if (r < 0) + return r; + + if (r == 0 && q == 0) + return true; + if (r == 0 && saved_n == n) + return false; + + ln[r] = ls[q] = 0; + + if (r != q || strcasecmp(ln, ls)) { + + /* Not the same, let's jump back, and try with the next label again */ + s = suffix; + n = saved_n; + saved_n = NULL; + } + } +} + +int dns_name_reverse(int family, const union in_addr_union *a, char **ret) { + const uint8_t *p; + int r; + + assert(a); + assert(ret); + + p = (const uint8_t*) a; + + if (family == AF_INET) + r = asprintf(ret, "%u.%u.%u.%u.in-addr.arpa", p[3], p[2], p[1], p[0]); + else if (family == AF_INET6) + r = asprintf(ret, "%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.ip6.arpa", + hexchar(p[15]), hexchar(p[14]), hexchar(p[13]), hexchar(p[12]), + hexchar(p[11]), hexchar(p[10]), hexchar(p[ 9]), hexchar(p[ 8]), + hexchar(p[ 7]), hexchar(p[ 6]), hexchar(p[ 5]), hexchar(p[ 4]), + hexchar(p[ 3]), hexchar(p[ 2]), hexchar(p[ 1]), hexchar(p[ 0])); + else + return -EAFNOSUPPORT; + if (r < 0) + return -ENOMEM; + + return 0; +} + +int dns_name_root(const char *name) { + char label[DNS_LABEL_MAX+1]; + int r; + + assert(name); + + r = dns_label_unescape(&name, label, sizeof(label)); + if (r < 0) + return r; + + return r == 0 && *name == 0; +} + +int dns_name_single_label(const char *name) { + char label[DNS_LABEL_MAX+1]; + int r; + + assert(name); + + r = dns_label_unescape(&name, label, sizeof(label)); + if (r < 0) + return r; + + if (r == 0) + return 0; + + r = dns_label_unescape(&name, label, sizeof(label)); + if (r < 0) + return r; + + return r == 0 && *name == 0; +} diff --git a/src/resolve/resolved-dns-domain.h b/src/resolve/resolved-dns-domain.h new file mode 100644 index 000000000..d6b4bdd08 --- /dev/null +++ b/src/resolve/resolved-dns-domain.h @@ -0,0 +1,43 @@ +/*-*- 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 . + ***/ + +#include + +#include "hashmap.h" +#include "in-addr-util.h" + +#define DNS_LABEL_MAX 63 + +int dns_label_unescape(const char **name, char *dest, size_t sz); +int dns_label_escape(const char *p, size_t l, char **ret); + +int dns_name_normalize(const char *s, char **_ret); + +unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_SIZE]); +int dns_name_compare_func(const void *a, const void *b); + +int dns_name_equal(const char *x, const char *y); +int dns_name_endswith(const char *name, const char *suffix); + +int dns_name_reverse(int family, const union in_addr_union *a, char **ret); + +int dns_name_root(const char *name); +int dns_name_single_label(const char *name); diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c new file mode 100644 index 000000000..2a666924b --- /dev/null +++ b/src/resolve/resolved-dns-packet.c @@ -0,0 +1,734 @@ +/*-*- 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 . + ***/ + +#include "utf8.h" + +#include "resolved-dns-domain.h" +#include "resolved-dns-packet.h" + +int dns_packet_new(DnsPacket **ret, size_t mtu) { + DnsPacket *p; + size_t a; + + assert(ret); + + if (mtu <= 0) + a = DNS_PACKET_SIZE_START; + else + a = mtu; + + if (a < DNS_PACKET_HEADER_SIZE) + a = DNS_PACKET_HEADER_SIZE; + + p = malloc0(ALIGN(sizeof(DnsPacket)) + a); + if (!p) + return -ENOMEM; + + p->size = p->rindex = DNS_PACKET_HEADER_SIZE; + p->allocated = a; + p->n_ref = 1; + + *ret = p; + + return 0; +} + +int dns_packet_new_query(DnsPacket **ret, size_t mtu) { + DnsPacket *p; + DnsPacketHeader *h; + int r; + + assert(ret); + + r = dns_packet_new(&p, mtu); + if (r < 0) + return r; + + h = DNS_PACKET_HEADER(p); + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0, 0, 0, 0, 1, 0, 0, 0, 0)); + + *ret = p; + return 0; +} + +DnsPacket *dns_packet_ref(DnsPacket *p) { + + if (!p) + return NULL; + + assert(p->n_ref > 0); + p->n_ref++; + return p; +} + +static void dns_packet_free(DnsPacket *p) { + char *s; + + assert(p); + + while ((s = hashmap_steal_first_key(p->names))) + free(s); + hashmap_free(p->names); + + free(p->data); + free(p); +} + +DnsPacket *dns_packet_unref(DnsPacket *p) { + if (!p) + return NULL; + + assert(p->n_ref > 0); + + if (p->n_ref == 1) + dns_packet_free(p); + else + p->n_ref--; + + return NULL; +} + +int dns_packet_validate(DnsPacket *p) { + assert(p); + + if (p->size < DNS_PACKET_HEADER_SIZE) + return -EBADMSG; + + return 0; +} + +int dns_packet_validate_reply(DnsPacket *p) { + DnsPacketHeader *h; + int r; + + assert(p); + + r = dns_packet_validate(p); + if (r < 0) + return r; + + h = DNS_PACKET_HEADER(p); + + /* Check QR field */ + if ((be16toh(h->flags) & 1) == 0) + return -EBADMSG; + + /* Check opcode field */ + if (((be16toh(h->flags) >> 1) & 15) != 0) + return -EBADMSG; + + return 0; +} + +static int dns_packet_extend(DnsPacket *p, size_t add, void **ret, size_t *start) { + assert(p); + + if (p->size + add > p->allocated) + return -ENOMEM; + + if (start) + *start = p->size; + + if (ret) + *ret = (uint8_t*) DNS_PACKET_DATA(p) + p->size; + + p->size += add; + return 0; +} + +static void dns_packet_truncate(DnsPacket *p, size_t sz) { + Iterator i; + char *s; + void *n; + + assert(p); + + if (p->size <= sz) + return; + + HASHMAP_FOREACH_KEY(s, n, p->names, i) { + + if (PTR_TO_SIZE(n) < sz) + continue; + + hashmap_remove(p->names, s); + free(s); + } + + p->size = sz; +} + +int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start) { + void *d; + int r; + + assert(p); + + r = dns_packet_extend(p, sizeof(uint8_t), &d, start); + if (r < 0) + return r; + + ((uint8_t*) d)[0] = v; + + return 0; +} + +int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start) { + void *d; + int r; + + assert(p); + + r = dns_packet_extend(p, sizeof(uint16_t), &d, start); + if (r < 0) + return r; + + ((uint8_t*) d)[0] = (uint8_t) (v >> 8); + ((uint8_t*) d)[1] = (uint8_t) (v & 255); + + return 0; +} + +int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start) { + void *d; + size_t l; + int r; + + assert(p); + assert(s); + + l = strlen(s); + if (l > 255) + return -E2BIG; + + r = dns_packet_extend(p, 1 + l, &d, start); + if (r < 0) + return r; + + ((uint8_t*) d)[0] = (uint8_t) l; + memcpy(((uint8_t*) d) + 1, s, l); + + return 0; +} + +int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start) { + void *w; + int r; + + assert(p); + assert(d); + + if (l > DNS_LABEL_MAX) + return -E2BIG; + + r = dns_packet_extend(p, 1 + l, &w, start); + if (r < 0) + return r; + + ((uint8_t*) w)[0] = (uint8_t) l; + memcpy(((uint8_t*) w) + 1, d, l); + + return 0; +} + +int dns_packet_append_name(DnsPacket *p, const char *name, size_t *start) { + size_t saved_size; + int r; + + assert(p); + assert(name); + + saved_size = p->size; + + while (*name) { + _cleanup_free_ char *s = NULL; + char label[DNS_LABEL_MAX]; + size_t n; + + n = PTR_TO_SIZE(hashmap_get(p->names, name)); + if (n > 0) { + assert(n < p->size); + + if (n < 0x4000) { + r = dns_packet_append_uint16(p, 0xC000 | n, NULL); + if (r < 0) + goto fail; + + goto done; + } + } + + s = strdup(name); + if (!s) { + r = -ENOMEM; + goto fail; + } + + r = dns_label_unescape(&name, label, sizeof(label)); + if (r < 0) + goto fail; + + r = dns_packet_append_label(p, label, r, &n); + if (r < 0) + goto fail; + + r = hashmap_ensure_allocated(&p->names, dns_name_hash_func, dns_name_compare_func); + if (r < 0) + goto fail; + + r = hashmap_put(p->names, s, SIZE_TO_PTR(n)); + if (r < 0) + goto fail; + + s = NULL; + } + + r = dns_packet_append_uint8(p, 0, NULL); + if (r < 0) + return r; + +done: + if (start) + *start = saved_size; + + return 0; + +fail: + dns_packet_truncate(p, saved_size); + return r; +} + +int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start) { + size_t saved_size; + int r; + + assert(p); + assert(k); + + saved_size = p->size; + + r = dns_packet_append_name(p, k->name, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, k->type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, k->class, NULL); + if (r < 0) + goto fail; + + if (start) + *start = saved_size; + + 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); + + if (p->rindex + sz > p->size) + return -EMSGSIZE; + + if (ret) + *ret = (uint8_t*) DNS_PACKET_DATA(p) + p->rindex; + + if (start) + *start = p->rindex; + + p->rindex += sz; + return 0; +} + +static void dns_packet_rewind(DnsPacket *p, size_t idx) { + assert(p); + assert(idx <= p->size); + assert(idx >= DNS_PACKET_HEADER_SIZE); + + p->rindex = idx; +} + +int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start) { + const void *d; + int r; + + assert(p); + + r = dns_packet_read(p, sizeof(uint8_t), &d, start); + if (r < 0) + return r; + + *ret = ((uint8_t*) d)[0]; + return 0; +} + +int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start) { + const void *d; + int r; + + assert(p); + + r = dns_packet_read(p, sizeof(uint16_t), &d, start); + if (r < 0) + return r; + + *ret = (((uint16_t) ((uint8_t*) d)[0]) << 8) | + ((uint16_t) ((uint8_t*) d)[1]); + return 0; +} + +int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start) { + const void *d; + int r; + + assert(p); + + r = dns_packet_read(p, sizeof(uint32_t), &d, start); + if (r < 0) + return r; + + *ret = (((uint32_t) ((uint8_t*) d)[0]) << 24) | + (((uint32_t) ((uint8_t*) d)[1]) << 16) | + (((uint32_t) ((uint8_t*) d)[2]) << 8) | + ((uint32_t) ((uint8_t*) d)[3]); + + return 0; +} + +int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start) { + size_t saved_rindex; + const void *d; + char *t; + uint8_t c; + int r; + + assert(p); + + saved_rindex = p->rindex; + + r = dns_packet_read_uint8(p, &c, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read(p, c, &d, NULL); + if (r < 0) + goto fail; + + if (memchr(d, 0, c)) { + r = -EBADMSG; + goto fail; + } + + t = strndup(d, c); + if (!t) { + r = -ENOMEM; + goto fail; + } + + if (!utf8_is_valid(t)) { + free(t); + r = -EBADMSG; + goto fail; + } + + *ret = t; + + if (start) + *start = saved_rindex; + + return 0; + +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +int dns_packet_read_name(DnsPacket *p, char **_ret, size_t *start) { + size_t saved_rindex, after_rindex = 0; + _cleanup_free_ char *ret = NULL; + size_t n = 0, allocated = 0; + bool first = true; + int r; + + assert(p); + assert(_ret); + + saved_rindex = p->rindex; + + for (;;) { + uint8_t c, d; + + r = dns_packet_read_uint8(p, &c, NULL); + if (r < 0) + goto fail; + + if (c == 0) + /* End of name */ + break; + else if (c <= 63) { + _cleanup_free_ char *t = NULL; + const char *label; + + /* Literal label */ + r = dns_packet_read(p, c, (const void**) &label, NULL); + if (r < 0) + goto fail; + + r = dns_label_escape(label, c, &t); + if (r < 0) + goto fail; + + if (!GREEDY_REALLOC(ret, allocated, n + !first + strlen(t) + 1)) { + r = -ENOMEM; + goto fail; + } + + if (!first) + ret[n++] = '.'; + else + first = false; + + memcpy(ret + n, t, c); + n += r; + continue; + } else if ((c & 0xc0) == 0xc0) { + uint16_t ptr; + + /* Pointer */ + r = dns_packet_read_uint8(p, &d, NULL); + if (r < 0) + goto fail; + + ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d; + if (ptr < DNS_PACKET_HEADER_SIZE || ptr >= saved_rindex) { + r = -EBADMSG; + goto fail; + } + + if (after_rindex == 0) + after_rindex = p->rindex; + + p->rindex = ptr; + } else + goto fail; + } + + if (!GREEDY_REALLOC(ret, allocated, n + 1)) { + r = -ENOMEM; + goto fail; + } + + ret[n] = 0; + + if (after_rindex != 0) + p->rindex= after_rindex; + + *_ret = ret; + ret = NULL; + + if (start) + *start = saved_rindex; + + return 0; + +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +int dns_packet_read_key(DnsPacket *p, DnsResourceKey *ret, size_t *start) { + _cleanup_(dns_resource_key_free) DnsResourceKey k = {}; + size_t saved_rindex; + int r; + + assert(p); + assert(ret); + + saved_rindex = p->rindex; + + r = dns_packet_read_name(p, &k.name, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint16(p, &k.type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint16(p, &k.class, NULL); + if (r < 0) + goto fail; + + *ret = k; + zero(k); + + if (start) + *start = saved_rindex; + + return 0; +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + size_t saved_rindex, offset; + uint16_t rdlength; + const void *d; + int r; + + assert(p); + assert(ret); + + saved_rindex = p->rindex; + + rr = dns_resource_record_new(); + if (!rr) + goto fail; + + r = dns_packet_read_key(p, &rr->key, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->ttl, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint16(p, &rdlength, NULL); + if (r < 0) + goto fail; + + if (p->rindex + rdlength > p->size) { + r = -EBADMSG; + goto fail; + } + + offset = p->rindex; + + switch (rr->key.type) { + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + r = dns_packet_read_name(p, &rr->ptr.name, NULL); + break; + + case DNS_TYPE_HINFO: + r = dns_packet_read_string(p, &rr->hinfo.cpu, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_string(p, &rr->hinfo.os, NULL); + 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)); + 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)); + break; + + default: + r = dns_packet_read(p, rdlength, &d, NULL); + if (r < 0) + goto fail; + + rr->generic.data = memdup(d, rdlength); + if (!rr->generic.data) { + r = -ENOMEM; + goto fail; + } + + rr->generic.size = rdlength; + break; + } + if (r < 0) + goto fail; + if (p->rindex != offset + rdlength) { + r = -EBADMSG; + goto fail; + } + + *ret = rr; + rr = NULL; + + if (start) + *start = saved_rindex; + + return 0; +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +int dns_packet_skip_question(DnsPacket *p) { + int r; + + unsigned i, n; + assert(p); + + n = be16toh(DNS_PACKET_HEADER(p)->qdcount); + for (i = 0; i < n; i++) { + _cleanup_(dns_resource_key_free) DnsResourceKey key = {}; + + r = dns_packet_read_key(p, &key, NULL); + if (r < 0) + return r; + } + + return 0; +} + +static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = { + [DNS_RCODE_SUCCESS] = "SUCCESS", + [DNS_RCODE_FORMERR] = "FORMERR", + [DNS_RCODE_SERVFAIL] = "SERVFAIL", + [DNS_RCODE_NXDOMAIN] = "NXDOMAIN", + [DNS_RCODE_NOTIMP] = "NOTIMP", + [DNS_RCODE_REFUSED] = "REFUSED", + [DNS_RCODE_YXDOMAIN] = "YXDOMAIN", + [DNS_RCODE_YXRRSET] = "YRRSET", + [DNS_RCODE_NXRRSET] = "NXRRSET", + [DNS_RCODE_NOTAUTH] = "NOTAUTH", + [DNS_RCODE_NOTZONE] = "NOTZONE", + [DNS_RCODE_BADVERS] = "BADVERS", + [DNS_RCODE_BADKEY] = "BADKEY", + [DNS_RCODE_BADTIME] = "BADTIME", + [DNS_RCODE_BADMODE] = "BADMODE", + [DNS_RCODE_BADNAME] = "BADNAME", + [DNS_RCODE_BADALG] = "BADALG", + [DNS_RCODE_BADTRUNC] = "BADTRUNC", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_rcode, int); diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h new file mode 100644 index 000000000..18ed4ba0d --- /dev/null +++ b/src/resolve/resolved-dns-packet.h @@ -0,0 +1,132 @@ +/*-*- 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 . + ***/ + +typedef struct DnsPacketHeader DnsPacketHeader; +typedef struct DnsPacket DnsPacket; + +#include + +#include "macro.h" +#include "sparse-endian.h" +#include "hashmap.h" +#include "resolved-dns-rr.h" + +struct DnsPacketHeader { + uint16_t id; + be16_t flags; + be16_t qdcount; + be16_t ancount; + be16_t nscount; + be16_t arcount; +}; + +#define DNS_PACKET_HEADER_SIZE sizeof(DnsPacketHeader) +#define DNS_PACKET_SIZE_START 512 + +struct DnsPacket { + int n_ref; + size_t size, allocated, rindex; + Hashmap *names; /* For name compression */ + void *data; + int ifindex; +}; + +static inline uint8_t* DNS_PACKET_DATA(DnsPacket *p) { + if (_unlikely_(!p)) + return NULL; + + if (p->data) + return p->data; + + return ((uint8_t*) p) + ALIGN(sizeof(DnsPacket)); +} + +#define DNS_PACKET_HEADER(p) ((DnsPacketHeader*) DNS_PACKET_DATA(p)) + +int dns_packet_new(DnsPacket **p, size_t mtu); +int dns_packet_new_query(DnsPacket **p, size_t mtu); + +DnsPacket *dns_packet_ref(DnsPacket *p); +DnsPacket *dns_packet_unref(DnsPacket *p); + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsPacket*, dns_packet_unref); + +int dns_packet_validate(DnsPacket *p); +int dns_packet_validate_reply(DnsPacket *p); + +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_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_read(DnsPacket *p, size_t sz, const void **ret, 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); +int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start); +int dns_packet_read_name(DnsPacket *p, char **ret, size_t *start); +int dns_packet_read_key(DnsPacket *p, DnsResourceKey *ret, size_t *start); +int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start); + +int dns_packet_skip_question(DnsPacket *p); + +#define DNS_PACKET_MAKE_FLAGS(qr, opcode, aa, tc, rd, ra, ad, cd, rcode) \ + (((uint16_t) !!qr << 15) | \ + ((uint16_t) (opcode & 15) << 11) | \ + ((uint16_t) !!aa << 10) | \ + ((uint16_t) !!tc << 9) | \ + ((uint16_t) !!rd << 8) | \ + ((uint16_t) !!ra << 7) | \ + ((uint16_t) !!ad << 5) | \ + ((uint16_t) !!cd << 4) | \ + ((uint16_t) (rcode & 15))) + +#define DNS_PACKET_RCODE(p) (be16toh(DNS_PACKET_HEADER(p)->flags) & 15) + +enum { + DNS_RCODE_SUCCESS = 0, + DNS_RCODE_FORMERR = 1, + DNS_RCODE_SERVFAIL = 2, + DNS_RCODE_NXDOMAIN = 3, + DNS_RCODE_NOTIMP = 4, + DNS_RCODE_REFUSED = 5, + DNS_RCODE_YXDOMAIN = 6, + DNS_RCODE_YXRRSET = 7, + DNS_RCODE_NXRRSET = 8, + DNS_RCODE_NOTAUTH = 9, + DNS_RCODE_NOTZONE = 10, + DNS_RCODE_BADVERS = 16, + DNS_RCODE_BADSIG = 16, /* duplicate value! */ + DNS_RCODE_BADKEY = 17, + DNS_RCODE_BADTIME = 18, + DNS_RCODE_BADMODE = 19, + DNS_RCODE_BADNAME = 20, + DNS_RCODE_BADALG = 21, + DNS_RCODE_BADTRUNC = 22, + _DNS_RCODE_MAX_DEFINED +}; + +const char* dns_rcode_to_string(int i) _const_; +int dns_rcode_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c new file mode 100644 index 000000000..a9880524e --- /dev/null +++ b/src/resolve/resolved-dns-query.c @@ -0,0 +1,408 @@ +/*-*- 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 . +***/ + +#include "resolved-dns-query.h" +#include "resolved-dns-domain.h" + +#define TRANSACTION_TIMEOUT_USEC (5 * USEC_PER_SEC) +#define QUERY_TIMEOUT_USEC (30 * USEC_PER_SEC) +#define ATTEMPTS_MAX 8 + +DnsQueryTransaction* dns_query_transaction_free(DnsQueryTransaction *t) { + if (!t) + return NULL; + + sd_event_source_unref(t->timeout_event_source); + dns_packet_unref(t->packet); + + if (t->query) { + LIST_REMOVE(transactions_by_query, t->query->transactions, t); + hashmap_remove(t->query->manager->dns_query_transactions, UINT_TO_PTR(t->id)); + } + + if (t->scope) + LIST_REMOVE(transactions_by_scope, t->scope->transactions, t); + + free(t); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQueryTransaction*, dns_query_transaction_free); + +static int dns_query_transaction_new(DnsQuery *q, DnsQueryTransaction **ret, DnsScope *s) { + _cleanup_(dns_query_transaction_freep) DnsQueryTransaction *t = NULL; + int r; + + assert(q); + assert(s); + + r = hashmap_ensure_allocated(&q->manager->dns_query_transactions, NULL, NULL); + if (r < 0) + return r; + + t = new0(DnsQueryTransaction, 1); + if (!t) + return -ENOMEM; + + do + random_bytes(&t->id, sizeof(t->id)); + while (t->id == 0 || + hashmap_get(q->manager->dns_query_transactions, UINT_TO_PTR(t->id))); + + r = hashmap_put(q->manager->dns_query_transactions, UINT_TO_PTR(t->id), t); + if (r < 0) { + t->id = 0; + return r; + } + + LIST_PREPEND(transactions_by_query, q->transactions, t); + t->query = q; + + LIST_PREPEND(transactions_by_scope, s->transactions, t); + t->scope = s; + + if (ret) + *ret = t; + + t = NULL; + + return 0; +} + +static void dns_query_transaction_set_state(DnsQueryTransaction *t, DnsQueryState state) { + assert(t); + + if (t->state == state) + return; + + t->state = state; + + if (state != DNS_QUERY_SENT) + t->timeout_event_source = sd_event_source_unref(t->timeout_event_source); + + dns_query_finish(t->query); +} + +int dns_query_transaction_reply(DnsQueryTransaction *t, DnsPacket *p) { + assert(t); + assert(p); + + t->packet = dns_packet_ref(p); + + if (DNS_PACKET_RCODE(p) == DNS_RCODE_SUCCESS) { + if( be16toh(DNS_PACKET_HEADER(p)->ancount) > 0) + dns_query_transaction_set_state(t, DNS_QUERY_SUCCESS); + else + dns_query_transaction_set_state(t, DNS_QUERY_INVALID_REPLY); + } else + dns_query_transaction_set_state(t, DNS_QUERY_FAILURE); + + return 0; +} + +static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdata) { + DnsQueryTransaction *t = userdata; + int r; + + assert(s); + assert(t); + + /* Timeout reached? Try again, with a new server */ + dns_scope_next_dns_server(t->scope); + + r = dns_query_transaction_start(t); + if (r < 0) + dns_query_transaction_set_state(t, DNS_QUERY_FAILURE); + + return 0; +} + +int dns_query_transaction_start(DnsQueryTransaction *t) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + unsigned n; + int r; + + assert(t); + + t->timeout_event_source = sd_event_source_unref(t->timeout_event_source); + + if (t->n_attempts >= ATTEMPTS_MAX) { + dns_query_transaction_set_state(t, DNS_QUERY_ATTEMPTS_MAX); + return 0; + } + + t->n_attempts++; + + r = dns_packet_new_query(&p, 0); + if (r < 0) + return r; + + for (n = 0; n < t->query->n_keys; n++) { + r = dns_packet_append_key(p, &t->query->keys[n], NULL); + if (r < 0) + return r; + } + + DNS_PACKET_HEADER(p)->qdcount = htobe16(t->query->n_keys); + DNS_PACKET_HEADER(p)->id = t->id; + + r = dns_scope_send(t->scope, p); + if (r < 0) { + /* Couldn't send? Try immediately again, with a new server */ + dns_scope_next_dns_server(t->scope); + + return dns_query_transaction_start(t); + } + + if (r > 0) { + int q; + + q = sd_event_add_time(t->query->manager->event, &t->timeout_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + TRANSACTION_TIMEOUT_USEC, 0, on_transaction_timeout, t); + if (q < 0) + return q; + + dns_query_transaction_set_state(t, DNS_QUERY_SENT); + } else + dns_query_transaction_set_state(t, DNS_QUERY_SKIPPED); + + return r; +} + +DnsQuery *dns_query_free(DnsQuery *q) { + unsigned n; + + if (!q) + return NULL; + + sd_bus_message_unref(q->request); + dns_packet_unref(q->packet); + sd_event_source_unref(q->timeout_event_source); + + while (q->transactions) + dns_query_transaction_free(q->transactions); + + if (q->manager) + LIST_REMOVE(queries, q->manager->dns_queries, q); + + for (n = 0; n < q->n_keys; n++) + free(q->keys[n].name); + free(q->keys); + free(q); + + return NULL; +} + +int dns_query_new(Manager *m, DnsQuery **ret, DnsResourceKey *keys, unsigned n_keys) { + _cleanup_(dns_query_freep) DnsQuery *q = NULL; + DnsScope *s, *first = NULL; + DnsScopeMatch found = DNS_SCOPE_NO; + const char *name = NULL; + int n, r; + + assert(m); + + if (n_keys <= 0 || n_keys >= 65535) + return -EINVAL; + + assert(keys); + + q = new0(DnsQuery, 1); + if (!q) + return -ENOMEM; + + q->keys = new(DnsResourceKey, n_keys); + if (!q->keys) + return -ENOMEM; + + for (q->n_keys = 0; q->n_keys < n_keys; q->n_keys++) { + q->keys[q->n_keys].class = keys[q->n_keys].class; + q->keys[q->n_keys].type = keys[q->n_keys].type; + q->keys[q->n_keys].name = strdup(keys[q->n_keys].name); + if (!q->keys[q->n_keys].name) + return -ENOMEM; + + if (!name) + name = q->keys[q->n_keys].name; + else if (!dns_name_equal(name, q->keys[q->n_keys].name)) + return -EINVAL; + } + + LIST_PREPEND(queries, m->dns_queries, q); + q->manager = m; + + LIST_FOREACH(scopes, s, m->dns_scopes) { + DnsScopeMatch match; + + match = dns_scope_test(s, name); + if (match < 0) + return match; + + if (match == DNS_SCOPE_NO) + continue; + + found = match; + + if (match == DNS_SCOPE_YES) { + first = s; + break; + } else { + assert(match == DNS_SCOPE_MAYBE); + + if (!first) + first = s; + } + } + + if (found == DNS_SCOPE_NO) + return -ENETDOWN; + + r = dns_query_transaction_new(q, NULL, first); + if (r < 0) + return r; + + n = 1; + LIST_FOREACH(scopes, s, first->scopes_next) { + DnsScopeMatch match; + + match = dns_scope_test(s, name); + if (match < 0) + return match; + + if (match != found) + continue; + + r = dns_query_transaction_new(q, NULL, s); + if (r < 0) + return r; + + n++; + } + + if (ret) + *ret = q; + q = NULL; + + return n; +} + +static void dns_query_set_state(DnsQuery *q, DnsQueryState state) { + assert(q); + + if (q->state == state) + return; + + q->state = state; + + if (state == DNS_QUERY_SENT) + return; + + q->timeout_event_source = sd_event_source_unref(q->timeout_event_source); + + while (q->transactions) + dns_query_transaction_free(q->transactions); + + if (q->complete) + q->complete(q); +} + +static int on_query_timeout(sd_event_source *s, usec_t usec, void *userdata) { + DnsQuery *q = userdata; + + assert(s); + assert(q); + + dns_query_set_state(q, DNS_QUERY_TIMEOUT); + return 0; +} + +int dns_query_start(DnsQuery *q) { + DnsQueryTransaction *t; + int r; + + assert(q); + assert(q->state == DNS_QUERY_NULL); + + if (!q->transactions) + return -ENETDOWN; + + r = sd_event_add_time(q->manager->event, &q->timeout_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + QUERY_TIMEOUT_USEC, 0, on_query_timeout, q); + if (r < 0) + goto fail; + + dns_query_set_state(q, DNS_QUERY_SENT); + + LIST_FOREACH(transactions_by_query, t, q->transactions) { + + r = dns_query_transaction_start(t); + if (r < 0) + goto fail; + + if (q->state != DNS_QUERY_SENT) + break; + } + + return 0; + +fail: + while (q->transactions) + dns_query_transaction_free(q->transactions); + + return r; +} + +void dns_query_finish(DnsQuery *q) { + DnsQueryTransaction *t; + DnsQueryState state = DNS_QUERY_SKIPPED; + uint16_t rcode = 0; + + assert(q); + + if (q->state != DNS_QUERY_SENT) + return; + + LIST_FOREACH(transactions_by_query, t, q->transactions) { + + /* One of the transactions is still going on, let's wait for it */ + if (t->state == DNS_QUERY_SENT || t->state == DNS_QUERY_NULL) + return; + + /* One of the transactions is sucecssful, let's use it */ + if (t->state == DNS_QUERY_SUCCESS) { + q->packet = dns_packet_ref(t->packet); + dns_query_set_state(q, DNS_QUERY_SUCCESS); + return; + } + + if (t->state == DNS_QUERY_FAILURE) { + state = DNS_QUERY_FAILURE; + + if (rcode == 0 && t->packet) + rcode = DNS_PACKET_RCODE(t->packet); + + } else if (state == DNS_QUERY_SKIPPED && t->state != DNS_QUERY_SKIPPED) + state = t->state; + } + + if (state == DNS_QUERY_FAILURE) + q->rcode = rcode; + + dns_query_set_state(q, state); +} diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h new file mode 100644 index 000000000..a07f17470 --- /dev/null +++ b/src/resolve/resolved-dns-query.h @@ -0,0 +1,97 @@ +/*-*- 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 . +***/ + +#include + +#include "sd-bus.h" +#include "util.h" + +typedef struct DnsQuery DnsQuery; +typedef struct DnsQueryTransaction DnsQueryTransaction; + +#include "resolved.h" +#include "resolved-dns-scope.h" +#include "resolved-dns-rr.h" +#include "resolved-dns-packet.h" + +typedef enum DnsQueryState { + DNS_QUERY_NULL, + DNS_QUERY_SENT, + DNS_QUERY_FAILURE, + DNS_QUERY_SUCCESS, + DNS_QUERY_SKIPPED, + DNS_QUERY_TIMEOUT, + DNS_QUERY_ATTEMPTS_MAX, + DNS_QUERY_INVALID_REPLY, +} DnsQueryState; + +struct DnsQueryTransaction { + DnsQuery *query; + DnsScope *scope; + + DnsQueryState state; + uint16_t id; + + sd_event_source *timeout_event_source; + unsigned n_attempts; + + DnsPacket *packet; + + LIST_FIELDS(DnsQueryTransaction, transactions_by_query); + LIST_FIELDS(DnsQueryTransaction, transactions_by_scope); +}; + +struct DnsQuery { + Manager *manager; + + DnsResourceKey *keys; + unsigned n_keys; + + DnsQueryState state; + + sd_event_source *timeout_event_source; + + uint16_t rcode; + DnsPacket *packet; + + sd_bus_message *request; + unsigned char request_family; + const char *request_hostname; + union in_addr_union request_address; + + void (*complete)(DnsQuery* q); + + LIST_HEAD(DnsQueryTransaction, transactions); + LIST_FIELDS(DnsQuery, queries); +}; + +int dns_query_new(Manager *m, DnsQuery **q, DnsResourceKey *keys, unsigned n_keys); +DnsQuery *dns_query_free(DnsQuery *q); +int dns_query_start(DnsQuery *q); +void dns_query_finish(DnsQuery *q); + +DnsQueryTransaction* dns_query_transaction_free(DnsQueryTransaction *t); +int dns_query_transaction_start(DnsQueryTransaction *t); +int dns_query_transaction_reply(DnsQueryTransaction *t, DnsPacket *p); + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuery*, dns_query_free); diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c new file mode 100644 index 000000000..cb555cb4e --- /dev/null +++ b/src/resolve/resolved-dns-rr.c @@ -0,0 +1,76 @@ +/*-*- 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 . +***/ + +#include "resolved-dns-rr.h" + +void dns_resource_key_free(DnsResourceKey *key) { + if (!key) + return; + + free(key->name); + zero(*key); +} + +DnsResourceRecord* dns_resource_record_new(void) { + DnsResourceRecord *rr; + + rr = new0(DnsResourceRecord, 1); + if (!rr) + return NULL; + + rr->n_ref = 1; + return rr; +} + +DnsResourceRecord* dns_resource_record_ref(DnsResourceRecord *rr) { + if (!rr) + return NULL; + + assert(rr->n_ref > 0); + rr->n_ref++; + + return rr; +} + +DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr) { + if (!rr) + return NULL; + + assert(rr->n_ref > 0); + + if (rr->n_ref > 1) { + rr->n_ref--; + return NULL; + } + + if (IN_SET(rr->key.type, DNS_TYPE_PTR, DNS_TYPE_NS, DNS_TYPE_CNAME)) + free(rr->ptr.name); + else if (rr->key.type == DNS_TYPE_HINFO) { + free(rr->hinfo.cpu); + free(rr->hinfo.os); + } else if (!IN_SET(rr->key.type, DNS_TYPE_A, DNS_TYPE_AAAA)) + free(rr->generic.data); + + dns_resource_key_free(&rr->key); + free(rr); + + return NULL; +} diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h new file mode 100644 index 000000000..144fffa3e --- /dev/null +++ b/src/resolve/resolved-dns-rr.h @@ -0,0 +1,114 @@ +/*-*- 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 . + ***/ + +#include +#include + +#include "util.h" + +typedef struct DnsResourceKey DnsResourceKey; +typedef struct DnsResourceRecord DnsResourceRecord; + +/* DNS record classes, see RFC 1035 */ +enum { + DNS_CLASS_IN = 0x01, +}; + +/* DNS record types, see RFC 1035 */ +enum { + /* Normal records */ + DNS_TYPE_A = 0x01, + DNS_TYPE_NS = 0x02, + DNS_TYPE_CNAME = 0x05, + DNS_TYPE_SOA = 0x06, + DNS_TYPE_PTR = 0x0C, + DNS_TYPE_HINFO = 0x0D, + DNS_TYPE_MX = 0x0F, + DNS_TYPE_TXT = 0x10, + DNS_TYPE_AAAA = 0x1C, + DNS_TYPE_SRV = 0x21, + + /* Special records */ + DNS_TYPE_ANY = 0xFF, + DNS_TYPE_OPT = 0x29, /* EDNS0 option */ + DNS_TYPE_TKEY = 0xF9, + DNS_TYPE_TSIG = 0xFA, + DNS_TYPE_IXFR = 0xFB, + DNS_TYPE_AXFR = 0xFC, +}; + +struct DnsResourceKey { + uint16_t class; + uint16_t type; + char *name; +}; + +struct DnsResourceRecord { + unsigned n_ref; + + DnsResourceKey key; + uint32_t ttl; + + union { + struct { + void *data; + uint16_t size; + } generic; + + /* struct { */ + /* uint16_t priority; */ + /* uint16_t weight; */ + /* uint16_t port; */ + /* char *name; */ + /* } srv; */ + + struct { + char *name; + } ptr, ns, cname; + + struct { + char *cpu; + char *os; + } hinfo; + + /* struct { */ + /* char **list; */ + /* } txt; */ + + struct { + struct in_addr in_addr; + } a; + + struct { + struct in6_addr in6_addr; + } aaaa; + }; +}; + +void dns_resource_key_free(DnsResourceKey *key); + +DnsResourceRecord* dns_resource_record_new(void); +DnsResourceRecord* dns_resource_record_ref(DnsResourceRecord *rr); +DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr); + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceRecord*, dns_resource_record_unref); diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c new file mode 100644 index 000000000..4e0a74276 --- /dev/null +++ b/src/resolve/resolved-dns-scope.c @@ -0,0 +1,147 @@ +/*-*- 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 . +***/ + +#include "strv.h" +#include "resolved-dns-domain.h" +#include "resolved-dns-scope.h" + +#define SEND_TIMEOUT_USEC (2*USEC_PER_SEC) + +int dns_scope_new(Manager *m, DnsScope **ret, DnsScopeType t) { + DnsScope *s; + + assert(m); + assert(ret); + + s = new0(DnsScope, 1); + if (!s) + return -ENOMEM; + + s->manager = m; + s->type = t; + + LIST_PREPEND(scopes, m->dns_scopes, s); + + *ret = s; + return 0; +} + +DnsScope* dns_scope_free(DnsScope *s) { + if (!s) + return NULL; + + while (s->transactions) { + DnsQuery *q; + + q = s->transactions->query; + dns_query_transaction_free(s->transactions); + + dns_query_finish(q); + } + + LIST_REMOVE(scopes, s->manager->dns_scopes, s); + strv_free(s->domains); + free(s); + + return NULL; +} + +DnsServer *dns_scope_get_server(DnsScope *s) { + assert(s); + + if (s->link) + return link_get_dns_server(s->link); + else + return manager_get_dns_server(s->manager); +} + +void dns_scope_next_dns_server(DnsScope *s) { + assert(s); + + if (s->link) + link_next_dns_server(s->link); + else + manager_next_dns_server(s->manager); +} + +int dns_scope_send(DnsScope *s, DnsPacket *p) { + int ifindex = 0; + DnsServer *srv; + int r; + + assert(s); + assert(p); + + srv = dns_scope_get_server(s); + if (!srv) + return 0; + + if (s->link) + ifindex = s->link->ifindex; + + if (srv->family == AF_INET) + r = manager_dns_ipv4_send(s->manager, srv, ifindex, p); + else if (srv->family == AF_INET6) + r = manager_dns_ipv6_send(s->manager, srv, ifindex, p); + else + return -EAFNOSUPPORT; + + if (r < 0) + return r; + + return 1; +} + +DnsScopeMatch dns_scope_test(DnsScope *s, const char *domain) { + char **i; + + assert(s); + assert(domain); + + STRV_FOREACH(i, s->domains) + if (dns_name_endswith(domain, *i)) + return DNS_SCOPE_YES; + + if (dns_name_root(domain)) + return DNS_SCOPE_NO; + + if (s->type == DNS_SCOPE_MDNS) { + if (dns_name_endswith(domain, "254.169.in-addr.arpa") || + dns_name_endswith(domain, "0.8.e.f.ip6.arpa")) + return DNS_SCOPE_YES; + else if (dns_name_endswith(domain, "local") || + !dns_name_single_label(domain)) + return DNS_SCOPE_MAYBE; + + return DNS_SCOPE_NO; + } + + if (s->type == DNS_SCOPE_DNS) { + if (dns_name_endswith(domain, "254.169.in-addr.arpa") || + dns_name_endswith(domain, "0.8.e.f.ip6.arpa") || + dns_name_single_label(domain)) + return DNS_SCOPE_NO; + + return DNS_SCOPE_MAYBE; + } + + assert_not_reached("Unknown scope type"); +} diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h new file mode 100644 index 000000000..50f1106bf --- /dev/null +++ b/src/resolve/resolved-dns-scope.h @@ -0,0 +1,69 @@ +/*-*- 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 . +***/ + +#include "list.h" + +typedef struct DnsScope DnsScope; + +#include "resolved.h" +#include "resolved-link.h" +#include "resolved-dns-server.h" +#include "resolved-dns-packet.h" +#include "resolved-dns-query.h" + +typedef enum DnsScopeType { + DNS_SCOPE_DNS, + DNS_SCOPE_MDNS, +} DnsScopeType; + +typedef enum DnsScopeMatch { + DNS_SCOPE_NO, + DNS_SCOPE_MAYBE, + DNS_SCOPE_YES, + _DNS_SCOPE_MATCH_MAX, + _DNS_SCOPE_INVALID = -1 +} DnsScopeMatch; + +struct DnsScope { + Manager *manager; + + DnsScopeType type; + unsigned char family; + + Link *link; + + char **domains; + + LIST_HEAD(DnsQueryTransaction, transactions); + + LIST_FIELDS(DnsScope, scopes); +}; + +int dns_scope_new(Manager *m, DnsScope **ret, DnsScopeType t); +DnsScope* dns_scope_free(DnsScope *s); + +int dns_scope_send(DnsScope *s, DnsPacket *p); +DnsScopeMatch dns_scope_test(DnsScope *s, const char *domain); + +DnsServer *dns_scope_get_server(DnsScope *s); +void dns_scope_next_dns_server(DnsScope *s); diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c new file mode 100644 index 000000000..4e0220b7d --- /dev/null +++ b/src/resolve/resolved-dns-server.c @@ -0,0 +1,98 @@ +/*-*- 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 . +***/ + +#include "resolved-dns-server.h" + +int dns_server_new( + Manager *m, + DnsServer **ret, + DnsServerSource source, + Link *l, + unsigned char family, + union in_addr_union *in_addr) { + + DnsServer *s, *tail; + + assert(m); + assert(in_addr); + assert(source < _DNS_SERVER_SOURCE_MAX); + + s = new0(DnsServer, 1); + if (!s) + return -ENOMEM; + + s->source = source; + s->family = family; + s->address = *in_addr; + + if (source == DNS_SERVER_LINK) { + assert(l); + LIST_FIND_TAIL(servers, l->link_dns_servers, tail); + LIST_INSERT_AFTER(servers, l->link_dns_servers, tail, s); + s->link = l; + } else if (source == DNS_SERVER_DHCP) { + assert(l); + LIST_FIND_TAIL(servers, l->dhcp_dns_servers, tail); + LIST_INSERT_AFTER(servers, l->dhcp_dns_servers, tail, s); + s->link = l; + } else { + assert(!l); + LIST_FIND_TAIL(servers, m->dns_servers, tail); + LIST_INSERT_AFTER(servers, m->dns_servers, tail, s); + } + + s->manager = m; + + if (ret) + *ret = s; + + return 0; +} + +DnsServer* dns_server_free(DnsServer *s) { + if (!s) + return NULL; + + if (s->source == DNS_SERVER_LINK) { + + if (s->link) + LIST_REMOVE(servers, s->link->link_dns_servers, s); + } else if (s->source == DNS_SERVER_DHCP) { + + if (s->link) + LIST_REMOVE(servers, s->link->dhcp_dns_servers, s); + + } else if (s->source == DNS_SERVER_SYSTEM) { + + if (s->manager) + LIST_REMOVE(servers, s->manager->dns_servers, s); + } + + if (s->link && s->link->current_dns_server == s) + s->link->current_dns_server = NULL; + + if (s->manager && s->manager->current_dns_server == s) + s->manager->current_dns_server = NULL; + + free(s); + + return NULL; +} diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h new file mode 100644 index 000000000..adceb868a --- /dev/null +++ b/src/resolve/resolved-dns-server.h @@ -0,0 +1,61 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +typedef struct DnsServer DnsServer; +typedef enum DnsServerSource DnsServerSource; + +#include "in-addr-util.h" +#include "resolved.h" +#include "resolved-link.h" +#include "resolved-dns-server.h" + +enum DnsServerSource { + DNS_SERVER_SYSTEM, + DNS_SERVER_LINK, + DNS_SERVER_DHCP, + _DNS_SERVER_SOURCE_MAX +}; + +struct DnsServer { + Manager *manager; + DnsServerSource source; + + unsigned char family; + union in_addr_union address; + + bool marked; + + Link *link; + + LIST_FIELDS(DnsServer, servers); +}; + +int dns_server_new( + Manager *m, + DnsServer **s, + DnsServerSource source, + Link *l, + unsigned char family, + union in_addr_union *in_addr); + +DnsServer* dns_server_free(DnsServer *s); diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf index 71e998051..051ccec70 100644 --- a/src/resolve/resolved-gperf.gperf +++ b/src/resolve/resolved-gperf.gperf @@ -14,4 +14,4 @@ struct ConfigPerfItem; %struct-type %includes %% -Resolve.DNS, config_parse_dnsv, 0, offsetof(Manager, fallback_dns) +Resolve.DNS, config_parse_dnsv, 0, 0 diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c new file mode 100644 index 000000000..27477de7b --- /dev/null +++ b/src/resolve/resolved-link.c @@ -0,0 +1,378 @@ +/*-*- 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 . +***/ + +#include + +#include "sd-network.h" +#include "dhcp-lease-internal.h" +#include "strv.h" +#include "resolved-link.h" + +int link_new(Manager *m, Link **ret, int ifindex) { + _cleanup_(link_freep) Link *l = NULL; + int r; + + assert(m); + assert(ifindex > 0); + + r = hashmap_ensure_allocated(&m->links, NULL, NULL); + if (r < 0) + return r; + + l = new0(Link, 1); + if (!l) + return -ENOMEM; + + l->ifindex = ifindex; + + r = hashmap_put(m->links, INT_TO_PTR(ifindex), l); + if (r < 0) + return r; + + l->manager = m; + + if (ret) + *ret = l; + l = NULL; + + return 0; +} + +Link *link_free(Link *l) { + + if (!l) + return NULL; + + while (l->addresses) + link_address_free(l->addresses); + + if (l->manager) + hashmap_remove(l->manager->links, INT_TO_PTR(l->ifindex)); + + dns_scope_free(l->unicast_scope); + dns_scope_free(l->mdns_ipv4_scope); + dns_scope_free(l->mdns_ipv6_scope); + + while (l->dhcp_dns_servers) + dns_server_free(l->dhcp_dns_servers); + + while (l->link_dns_servers) + dns_server_free(l->link_dns_servers); + + free(l); + return NULL; + } + +int link_update_rtnl(Link *l, sd_rtnl_message *m) { + int r; + + assert(l); + assert(m); + + r = sd_rtnl_message_link_get_flags(m, &l->flags); + if (r < 0) + return r; + + return 0; +} + +static int update_dhcp_dns_servers(Link *l) { + _cleanup_dhcp_lease_unref_ sd_dhcp_lease *lease = NULL; + _cleanup_free_ struct in_addr *nameservers = NULL; + DnsServer *s, *nx; + unsigned i; + size_t n; + int r; + + assert(l); + + r = sd_network_dhcp_use_dns(l->ifindex); + if (r <= 0) + goto clear; + + r = sd_network_get_dhcp_lease(l->ifindex, &lease); + if (r < 0) + goto clear; + + LIST_FOREACH(servers, s, l->dhcp_dns_servers) + s->marked = true; + + r = sd_dhcp_lease_get_dns(lease, &nameservers, &n); + if (r < 0) + goto clear; + + for (i = 0; i < n; i++) { + union in_addr_union a = { .in = nameservers[i] }; + + s = link_find_dns_server(l, DNS_SERVER_DHCP, AF_INET, &a); + if (s) + s->marked = false; + else { + r = dns_server_new(l->manager, NULL, DNS_SERVER_DHCP, l, AF_INET, &a); + if (r < 0) + goto clear; + } + } + + LIST_FOREACH_SAFE(servers, s, nx, l->dhcp_dns_servers) + if (s->marked) + dns_server_free(s); + + return 0; + +clear: + while (l->dhcp_dns_servers) + dns_server_free(l->dhcp_dns_servers); + + return r; +} + +static int update_link_dns_servers(Link *l) { + _cleanup_free_ struct in_addr *nameservers = NULL; + _cleanup_free_ struct in6_addr *nameservers6 = NULL; + DnsServer *s, *nx; + unsigned i; + size_t n; + int r; + + assert(l); + + LIST_FOREACH(servers, s, l->link_dns_servers) + s->marked = true; + + r = sd_network_get_dns(l->ifindex, &nameservers, &n); + if (r < 0) + goto clear; + + for (i = 0; i < n; i++) { + union in_addr_union a = { .in = nameservers[i] }; + + s = link_find_dns_server(l, DNS_SERVER_LINK, AF_INET, &a); + if (s) + s->marked = false; + else { + r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, AF_INET, &a); + if (r < 0) + goto clear; + } + } + + r = sd_network_get_dns6(l->ifindex, &nameservers6, &n); + if (r < 0) + goto clear; + + for (i = 0; i < n; i++) { + union in_addr_union a = { .in6 = nameservers6[i] }; + + s = link_find_dns_server(l, DNS_SERVER_LINK, AF_INET6, &a); + if (s) + s->marked = false; + else { + r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, AF_INET6, &a); + if (r < 0) + goto clear; + } + } + + LIST_FOREACH_SAFE(servers, s, nx, l->link_dns_servers) + if (s->marked) + dns_server_free(s); + + return 0; + +clear: + while (l->link_dns_servers) + dns_server_free(l->link_dns_servers); + + return r; +} + +int link_update_monitor(Link *l) { + assert(l); + + free(l->operational_state); + l->operational_state = NULL; + + sd_network_get_link_operational_state(l->ifindex, &l->operational_state); + + update_dhcp_dns_servers(l); + update_link_dns_servers(l); + + return 0; +} + +bool link_relevant(Link *l) { + LinkAddress *a; + + assert(l); + + /* A link is relevant if it isn't a loopback device and has at + * least one relevant IP address */ + + if (l->flags & IFF_LOOPBACK) + return false; + + if (l->operational_state && !STR_IN_SET(l->operational_state, "unknown", "degraded", "routable")) + return false; + + LIST_FOREACH(addresses, a, l->addresses) + if (link_address_relevant(a)) + return true; + + return false; +} + +LinkAddress *link_find_address(Link *l, unsigned char family, union in_addr_union *in_addr) { + LinkAddress *a; + + assert(l); + + LIST_FOREACH(addresses, a, l->addresses) { + + if (a->family == family && + in_addr_equal(family, &a->in_addr, in_addr)) + return a; + } + + return NULL; +} + +DnsServer* link_find_dns_server(Link *l, DnsServerSource source, unsigned char family, union in_addr_union *in_addr) { + DnsServer *first, *s; + + assert(l); + + first = source == DNS_SERVER_DHCP ? l->dhcp_dns_servers : l->link_dns_servers; + + LIST_FOREACH(servers, s, first) { + + if (s->family == family && + in_addr_equal(family, &s->address, in_addr)) + return s; + } + + return NULL; +} + +DnsServer *link_get_dns_server(Link *l) { + assert(l); + + if (!l->current_dns_server) + l->current_dns_server = l->link_dns_servers; + if (!l->current_dns_server) + l->current_dns_server = l->dhcp_dns_servers; + + return l->current_dns_server; +} + +void link_next_dns_server(Link *l) { + assert(l); + + /* Switch to the next DNS server */ + + if (!l->current_dns_server) { + l->current_dns_server = l->link_dns_servers; + if (l->current_dns_server) + return; + } + + if (!l->current_dns_server) { + l->current_dns_server = l->dhcp_dns_servers; + if (l->current_dns_server) + return; + } + + if (!l->current_dns_server) + return; + + if (l->current_dns_server->servers_next) { + l->current_dns_server = l->current_dns_server->servers_next; + return; + } + + if (l->current_dns_server->source == DNS_SERVER_LINK) + l->current_dns_server = l->dhcp_dns_servers; + else { + assert(l->current_dns_server->source == DNS_SERVER_DHCP); + l->current_dns_server = l->link_dns_servers; + } +} + +int link_address_new(Link *l, LinkAddress **ret, unsigned char family, union in_addr_union *in_addr) { + LinkAddress *a; + + assert(l); + assert(in_addr); + + a = new0(LinkAddress, 1); + if (!a) + return -ENOMEM; + + a->family = family; + a->in_addr = *in_addr; + + a->link = l; + LIST_PREPEND(addresses, l->addresses, a); + + if (ret) + *ret = a; + + return 0; +} + +LinkAddress *link_address_free(LinkAddress *a) { + if (!a) + return NULL; + + if (a->link) + LIST_REMOVE(addresses, a->link->addresses, a); + + free(a); + return NULL; +} + +int link_address_update_rtnl(LinkAddress *a, sd_rtnl_message *m) { + int r; + assert(a); + assert(m); + + r = sd_rtnl_message_addr_get_flags(m, &a->flags); + if (r < 0) + return r; + + r = sd_rtnl_message_addr_get_scope(m, &a->scope); + if (r < 0) + return r; + + return 0; +} + +bool link_address_relevant(LinkAddress *a) { + assert(a); + + if (a->flags & IFA_F_DEPRECATED) + return false; + + if (IN_SET(a->scope, RT_SCOPE_HOST, RT_SCOPE_NOWHERE)) + return false; + + return true; +} diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h new file mode 100644 index 000000000..07f68ab41 --- /dev/null +++ b/src/resolve/resolved-link.h @@ -0,0 +1,84 @@ +/*-*- 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 . +***/ + +#include "in-addr-util.h" +#include "ratelimit.h" + +typedef struct Link Link; +typedef struct LinkAddress LinkAddress; + +#include "resolved.h" +#include "resolved-dns-server.h" +#include "resolved-dns-scope.h" + +struct LinkAddress { + Link *link; + + unsigned char family; + union in_addr_union in_addr; + + unsigned char flags, scope; + + LIST_FIELDS(LinkAddress, addresses); +}; + +struct Link { + Manager *manager; + + int ifindex; + unsigned flags; + + LIST_HEAD(LinkAddress, addresses); + + LIST_HEAD(DnsServer, link_dns_servers); + LIST_HEAD(DnsServer, dhcp_dns_servers); + DnsServer *current_dns_server; + + DnsScope *unicast_scope; + DnsScope *mdns_ipv4_scope; + DnsScope *mdns_ipv6_scope; + + size_t mtu; + + char *operational_state; + + RateLimit mdns_ratelimit; +}; + +int link_new(Manager *m, Link **ret, int ifindex); +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); +LinkAddress* link_find_address(Link *l, unsigned char family, union in_addr_union *in_addr); + +DnsServer* link_find_dns_server(Link *l, DnsServerSource source, unsigned char family, 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, unsigned char family, 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); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_free); diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index b4f4d0714..dc6deeb7e 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -22,16 +22,286 @@ #include #include #include +#include +#include +#include -#include "resolved.h" +#include "rtnl-util.h" #include "event-util.h" #include "network-util.h" #include "sd-dhcp-lease.h" #include "dhcp-lease-internal.h" #include "network-internal.h" #include "conf-parser.h" +#include "socket-util.h" +#include "resolved.h" + +#define SEND_TIMEOUT_USEC (200 * USEC_PER_MSEC) + +static int manager_process_link(sd_rtnl *rtnl, sd_rtnl_message *mm, void *userdata) { + Manager *m = userdata; + uint16_t type; + Link *l; + int ifindex, r; + + assert(rtnl); + assert(m); + assert(mm); + + r = sd_rtnl_message_get_type(mm, &type); + if (r < 0) + goto fail; + + r = sd_rtnl_message_link_get_ifindex(mm, &ifindex); + if (r < 0) + goto fail; + + l = hashmap_get(m->links, INT_TO_PTR(ifindex)); + + switch (type) { + + case RTM_NEWLINK: + if (!l) { + log_debug("Found link %i", ifindex); + + r = link_new(m, &l, ifindex); + if (r < 0) + goto fail; + } + + r = link_update_rtnl(l, mm); + if (r < 0) + goto fail; + + break; + + case RTM_DELLINK: + if (l) { + log_debug("Removing link %i", l->ifindex); + link_free(l); + } + + break; + } + + return 0; + +fail: + log_warning("Failed to process RTNL link message: %s", strerror(-r)); + return 0; +} + +static int manager_process_address(sd_rtnl *rtnl, sd_rtnl_message *mm, void *userdata) { + Manager *m = userdata; + union in_addr_union address; + unsigned char family; + uint16_t type; + int r, ifindex; + LinkAddress *a; + Link *l; + + assert(rtnl); + assert(mm); + assert(m); + + r = sd_rtnl_message_get_type(mm, &type); + if (r < 0) + goto fail; + + r = sd_rtnl_message_addr_get_ifindex(mm, &ifindex); + if (r < 0) + goto fail; + + l = hashmap_get(m->links, INT_TO_PTR(ifindex)); + if (!l) + return 0; + + r = sd_rtnl_message_addr_get_family(mm, &family); + if (r < 0) + goto fail; + + switch (family) { + + case AF_INET: + r = sd_rtnl_message_read_in_addr(mm, IFA_LOCAL, &address.in); + if (r < 0) { + r = sd_rtnl_message_read_in_addr(mm, IFA_ADDRESS, &address.in); + if (r < 0) + goto fail; + } + + break; + + case AF_INET6: + r = sd_rtnl_message_read_in6_addr(mm, IFA_LOCAL, &address.in6); + if (r < 0) { + r = sd_rtnl_message_read_in6_addr(mm, IFA_ADDRESS, &address.in6); + if (r < 0) + goto fail; + } + + break; + + default: + return 0; + } + + a = link_find_address(l, family, &address); + + switch (type) { + + case RTM_NEWADDR: + + if (!a) { + r = link_address_new(l, &a, family, &address); + if (r < 0) + return r; + } + + r = link_address_update_rtnl(a, mm); + if (r < 0) + return r; + + break; + + case RTM_DELADDR: + if (a) + link_address_free(a); + break; + } + + return 0; + +fail: + log_warning("Failed to process RTNL address message: %s", strerror(-r)); + return 0; +} + + +static int manager_rtnl_listen(Manager *m) { + _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL, *reply = NULL; + sd_rtnl_message *i; + int r; + + assert(m); + + /* First, subscibe to interfaces coming and going */ + r = sd_rtnl_open(&m->rtnl, 3, RTNLGRP_LINK, RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV6_IFADDR); + if (r < 0) + return r; + + r = sd_rtnl_attach_event(m->rtnl, m->event, 0); + if (r < 0) + return r; + + r = sd_rtnl_add_match(m->rtnl, RTM_NEWLINK, manager_process_link, m); + if (r < 0) + return r; + + r = sd_rtnl_add_match(m->rtnl, RTM_DELLINK, manager_process_link, m); + if (r < 0) + return r; + + r = sd_rtnl_add_match(m->rtnl, RTM_NEWADDR, manager_process_address, m); + if (r < 0) + return r; -static int set_fallback_dns(Manager *m, const char *string) { + r = sd_rtnl_add_match(m->rtnl, RTM_DELADDR, manager_process_address, m); + if (r < 0) + return r; + + /* Then, enumerate all links */ + r = sd_rtnl_message_new_link(m->rtnl, &req, RTM_GETLINK, 0); + if (r < 0) + return r; + + r = sd_rtnl_message_request_dump(req, true); + if (r < 0) + return r; + + r = sd_rtnl_call(m->rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (i = reply; i; i = sd_rtnl_message_next(i)) { + r = manager_process_link(m->rtnl, i, m); + if (r < 0) + return r; + } + + req = sd_rtnl_message_unref(req); + reply = sd_rtnl_message_unref(reply); + + /* Finally, enumerate all addresses, too */ + r = sd_rtnl_message_new_addr(m->rtnl, &req, RTM_GETADDR, 0, AF_UNSPEC); + if (r < 0) + return r; + + r = sd_rtnl_message_request_dump(req, true); + if (r < 0) + return r; + + r = sd_rtnl_call(m->rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (i = reply; i; i = sd_rtnl_message_next(i)) { + r = manager_process_address(m->rtnl, i, m); + if (r < 0) + return r; + } + + return r; +} + +static int on_network_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + Iterator i; + Link *l; + int r; + + assert(m); + + sd_network_monitor_flush(m->network_monitor); + + HASHMAP_FOREACH(l, m->links, i) { + r = link_update_monitor(l); + if (r < 0) + log_warning("Failed to update monitor information for %i: %s", l->ifindex, strerror(-r)); + } + + r = manager_write_resolv_conf(m); + if (r < 0) + log_warning("Could not update resolv.conf: %s", strerror(-r)); + + return 0; +} + +static int manager_network_monitor_listen(Manager *m) { + int r, fd, events; + + assert(m); + + r = sd_network_monitor_new(NULL, &m->network_monitor); + if (r < 0) + return r; + + fd = sd_network_monitor_get_fd(m->network_monitor); + if (fd < 0) + return fd; + + events = sd_network_monitor_get_events(m->network_monitor); + if (events < 0) + return events; + + r = sd_event_add_io(m->event, &m->network_event_source, fd, events, &on_network_event, m); + if (r < 0) + return r; + + return 0; +} + +static int parse_dns_server_string(Manager *m, const char *string) { char *word, *state; size_t length; int r; @@ -40,27 +310,26 @@ static int set_fallback_dns(Manager *m, const char *string) { assert(string); FOREACH_WORD_QUOTED(word, length, string, state) { - _cleanup_free_ Address *address = NULL; - Address *tail; - _cleanup_free_ char *addrstr = NULL; - - address = new0(Address, 1); - if (!address) - return -ENOMEM; + char buffer[length+1]; + unsigned family; + union in_addr_union addr; - addrstr = strndup(word, length); - if (!addrstr) - return -ENOMEM; + memcpy(buffer, word, length); + buffer[length] = 0; - r = net_parse_inaddr(addrstr, &address->family, &address->in_addr); + r = in_addr_from_string_auto(buffer, &family, &addr); if (r < 0) { - log_debug("Ignoring invalid DNS address '%s'", addrstr); + log_warning("Ignoring invalid DNS address '%s'", buffer); continue; } - LIST_FIND_TAIL(addresses, m->fallback_dns, tail); - LIST_INSERT_AFTER(addresses, m->fallback_dns, tail, address); - address = NULL; + /* filter out duplicates */ + if (manager_find_dns_server(m, family, &addr)) + continue; + + r = dns_server_new(m, NULL, DNS_SERVER_SYSTEM, NULL, family, &addr); + if (r < 0) + return r; } return 0; @@ -79,39 +348,48 @@ int config_parse_dnsv( void *userdata) { Manager *m = userdata; - Address *address; + int r; assert(filename); assert(lvalue); assert(rvalue); assert(m); - while ((address = m->fallback_dns)) { - LIST_REMOVE(addresses, m->fallback_dns, address); - free(address); + /* Empty assignment means clear the list */ + if (isempty(rvalue)) { + while (m->dns_servers) + dns_server_free(m->dns_servers); + + return 0; } - set_fallback_dns(m, rvalue); + r = parse_dns_server_string(m, rvalue); + if (r < 0) { + log_error("Failed to parse DNS server string"); + return r; + } return 0; } -static int manager_parse_config_file(Manager *m) { +int manager_parse_config_file(Manager *m) { int r; assert(m); - r = config_parse(NULL, "/etc/systemd/resolved.conf", NULL, - "Resolve\0", config_item_perf_lookup, (void*) resolved_gperf_lookup, + r = config_parse(NULL, + "/etc/systemd/resolved.conf", NULL, + "Resolve\0", + config_item_perf_lookup, (void*) resolved_gperf_lookup, false, false, m); if (r < 0) log_warning("Failed to parse configuration file: %s", strerror(-r)); - return r; + return 0; } int manager_new(Manager **ret) { - _cleanup_manager_free_ Manager *m = NULL; + _cleanup_(manager_freep) Manager *m = NULL; int r; assert(ret); @@ -120,11 +398,9 @@ int manager_new(Manager **ret) { if (!m) return -ENOMEM; - r = set_fallback_dns(m, DNS_SERVERS); - if (r < 0) - return r; + m->dns_ipv4_fd = m->dns_ipv6_fd = -1; - r = manager_parse_config_file(m); + r = parse_dns_server_string(m, /* "172.31.0.125 2001:4860:4860::8888 2001:4860:4860::8889" */ DNS_SERVERS); if (r < 0) return r; @@ -137,61 +413,96 @@ int manager_new(Manager **ret) { sd_event_set_watchdog(m->event, true); + r = dns_scope_new(m, &m->unicast_scope, DNS_SCOPE_DNS); + if (r < 0) + return r; + + r = manager_network_monitor_listen(m); + if (r < 0) + return r; + + r = manager_rtnl_listen(m); + if (r < 0) + return r; + + r = manager_connect_bus(m); + if (r < 0) + return r; + *ret = m; m = NULL; return 0; } -void manager_free(Manager *m) { - Address *address; +Manager *manager_free(Manager *m) { + Link *l; if (!m) - return; + return NULL; + + while (m->dns_queries) + dns_query_free(m->dns_queries); + + hashmap_free(m->dns_query_transactions); + + while ((l = hashmap_first(m->links))) + link_free(l); + hashmap_free(m->links); + + dns_scope_free(m->unicast_scope); + + while (m->dns_servers) + dns_server_free(m->dns_servers); sd_event_source_unref(m->network_event_source); sd_network_monitor_unref(m->network_monitor); - sd_event_unref(m->event); - while ((address = m->fallback_dns)) { - LIST_REMOVE(addresses, m->fallback_dns, address); - free(address); - } + sd_event_source_unref(m->dns_ipv4_event_source); + sd_event_source_unref(m->dns_ipv6_event_source); + + safe_close(m->dns_ipv4_fd); + safe_close(m->dns_ipv6_fd); + + sd_event_source_unref(m->bus_retry_event_source); + sd_bus_unref(m->bus); + sd_event_unref(m->event); free(m); + + return NULL; } -static void append_dns(FILE *f, void *dns, unsigned char family, unsigned *count) { - char buf[INET6_ADDRSTRLEN]; - const char *address; +static void write_resolve_conf_server(DnsServer *s, FILE *f, unsigned *count) { + _cleanup_free_ char *t = NULL; + int r; + assert(s); assert(f); - assert(dns); assert(count); - address = inet_ntop(family, dns, buf, INET6_ADDRSTRLEN); - if (!address) { + r = in_addr_to_string(s->family, &s->address, &t); + if (r < 0) { log_warning("Invalid DNS address. Ignoring."); return; } if (*count == MAXNS) - fputs("# Too many DNS servers configured, the following entries " - "may be ignored\n", f); - - fprintf(f, "nameserver %s\n", address); + fputs("# Too many DNS servers configured, the following entries may be ignored\n", f); + fprintf(f, "nameserver %s\n", t); (*count) ++; } -int manager_update_resolv_conf(Manager *m) { +int manager_write_resolv_conf(Manager *m) { const char *path = "/run/systemd/resolve/resolv.conf"; _cleanup_free_ char *temp_path = NULL; _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ unsigned *indices = NULL; - Address *address; unsigned count = 0; - int n, r, i; + DnsServer *s; + Iterator i; + Link *l; + int r; assert(m); @@ -207,108 +518,368 @@ int manager_update_resolv_conf(Manager *m) { "# resolv.conf(5) in a different way, replace the symlink by a\n" "# static file or a different symlink.\n\n", f); - n = sd_network_get_ifindices(&indices); - if (n < 0) - n = 0; - - for (i = 0; i < n; i++) { - _cleanup_dhcp_lease_unref_ sd_dhcp_lease *lease = NULL; - struct in_addr *nameservers; - struct in6_addr *nameservers6; - size_t nameservers_size; - - r = sd_network_dhcp_use_dns(indices[i]); - if (r > 0) { - r = sd_network_get_dhcp_lease(indices[i], &lease); - if (r >= 0) { - r = sd_dhcp_lease_get_dns(lease, &nameservers, &nameservers_size); - if (r >= 0) { - unsigned j; - - for (j = 0; j < nameservers_size; j++) - append_dns(f, &nameservers[j], AF_INET, &count); - } - } - } + HASHMAP_FOREACH(l, m->links, i) { + LIST_FOREACH(servers, s, l->link_dns_servers) + write_resolve_conf_server(s, f, &count); - r = sd_network_get_dns(indices[i], &nameservers, &nameservers_size); - if (r >= 0) { - unsigned j; + LIST_FOREACH(servers, s, l->dhcp_dns_servers) + write_resolve_conf_server(s, f, &count); + } - for (j = 0; j < nameservers_size; j++) - append_dns(f, &nameservers[j], AF_INET, &count); + LIST_FOREACH(servers, s, m->dns_servers) + write_resolve_conf_server(s, f, &count); - free(nameservers); - } + r = fflush_and_check(f); + if (r < 0) + goto fail; + + if (rename(temp_path, path) < 0) { + r = -errno; + goto fail; + } - r = sd_network_get_dns6(indices[i], &nameservers6, &nameservers_size); - if (r >= 0) { - unsigned j; + return 0; - for (j = 0; j < nameservers_size; j++) - append_dns(f, &nameservers6[j], AF_INET6, &count); +fail: + unlink(path); + unlink(temp_path); + return r; +} - free(nameservers6); - } +int manager_dns_ipv4_recv(Manager *m, DnsPacket **ret) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + struct msghdr mh = {}; + int fd, ms = 0, r; + struct iovec iov; + ssize_t l; + + assert(m); + assert(ret); + + fd = manager_dns_ipv4_fd(m); + if (fd < 0) + return fd; + + r = ioctl(fd, FIONREAD, &ms); + if (r < 0) + return -errno; + if (ms < 0) + return -EIO; + + r = dns_packet_new(&p, ms); + if (r < 0) + return r; + + iov.iov_base = DNS_PACKET_DATA(p); + iov.iov_len = p->allocated; + + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + + l = recvmsg(fd, &mh, 0); + if (l < 0) { + if (errno == EAGAIN) + return 0; + + return -errno; } - LIST_FOREACH(addresses, address, m->fallback_dns) - append_dns(f, &address->in_addr, address->family, &count); + if (l <= 0) + return -EIO; - fflush(f); + p->size = (size_t) l; - if (ferror(f) || rename(temp_path, path) < 0) { - r = -errno; - unlink(path); - unlink(temp_path); + *ret = p; + p = NULL; + + return 1; +} + +int manager_dns_ipv6_recv(Manager *m, DnsPacket **ret) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + struct msghdr mh = {}; + struct iovec iov; + int fd, ms = 0, r; + ssize_t l; + + assert(m); + assert(ret); + + fd = manager_dns_ipv6_fd(m); + if (fd < 0) + return fd; + + r = ioctl(fd, FIONREAD, &ms); + if (r < 0) + return -errno; + if (ms < 0) + return -EIO; + + r = dns_packet_new(&p, ms); + if (r < 0) return r; + + iov.iov_base = DNS_PACKET_DATA(p); + iov.iov_len = p->allocated; + + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + + l = recvmsg(fd, &mh, 0); + if (l < 0) { + if (errno == EAGAIN) + return 0; + + return -errno; } - return 0; + if (l <= 0) + return -EIO; + + p->size = (size_t) l; + + *ret = p; + p = NULL; + + return 1; +} + +static int on_dns_ipv4_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + DnsQueryTransaction *t = NULL; + Manager *m = userdata; + int r; + + r = manager_dns_ipv4_recv(m, &p); + if (r <= 0) + return r; + + t = hashmap_get(m->dns_query_transactions, UINT_TO_PTR(DNS_PACKET_HEADER(p)->id)); + if (!t) + return 0; + + return dns_query_transaction_reply(t, p); } -static int manager_network_event_handler(sd_event_source *s, int fd, uint32_t revents, - void *userdata) { +static int on_dns_ipv6_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + DnsQueryTransaction *t = NULL; Manager *m = userdata; int r; + r = manager_dns_ipv6_recv(m, &p); + if (r <= 0) + return r; + + t = hashmap_get(m->dns_query_transactions, UINT_TO_PTR(DNS_PACKET_HEADER(p)->id)); + if (!t) + return 0; + + return dns_query_transaction_reply(t, p); +} + +int manager_dns_ipv4_fd(Manager *m) { + int r; + assert(m); - r = manager_update_resolv_conf(m); - if (r < 0) - log_warning("Could not update resolv.conf: %s", strerror(-r)); + if (m->dns_ipv4_fd >= 0) + return m->dns_ipv4_fd; - sd_network_monitor_flush(m->network_monitor); + m->dns_ipv4_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->dns_ipv4_fd < 0) + return -errno; - return 0; + r = sd_event_add_io(m->event, &m->dns_ipv4_event_source, m->dns_ipv4_fd, EPOLLIN, on_dns_ipv4_packet, m); + if (r < 0) + return r; + + return m->dns_ipv4_fd; } -int manager_network_monitor_listen(Manager *m) { - _cleanup_event_source_unref_ sd_event_source *event_source = NULL; - _cleanup_network_monitor_unref_ sd_network_monitor *monitor = NULL; - int r, fd, events; +int manager_dns_ipv6_fd(Manager *m) { + int r; + + assert(m); - r = sd_network_monitor_new(NULL, &monitor); + if (m->dns_ipv6_fd >= 0) + return m->dns_ipv6_fd; + + m->dns_ipv6_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->dns_ipv6_fd < 0) + return -errno; + + r = sd_event_add_io(m->event, &m->dns_ipv6_event_source, m->dns_ipv6_fd, EPOLLIN, on_dns_ipv6_packet, m); if (r < 0) return r; - fd = sd_network_monitor_get_fd(monitor); + return m->dns_ipv6_fd; +} + +static int sendmsg_loop(int fd, struct msghdr *mh, int flags) { + int r; + + assert(fd >= 0); + assert(mh); + + for (;;) { + if (sendmsg(fd, mh, flags) >= 0) + return 0; + + if (errno == EINTR) + continue; + + if (errno != EAGAIN) + return -errno; + + r = fd_wait_for_event(fd, POLLOUT, SEND_TIMEOUT_USEC); + if (r < 0) + return r; + if (r == 0) + return -ETIMEDOUT; + } +} + +int manager_dns_ipv4_send(Manager *m, DnsServer *srv, int ifindex, DnsPacket *p) { + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(53), + }; + struct msghdr mh = {}; + struct iovec iov; + uint8_t control[CMSG_SPACE(sizeof(struct in_pktinfo))]; + int fd; + + assert(m); + assert(srv); + assert(p); + + fd = manager_dns_ipv4_fd(m); if (fd < 0) return fd; - events = sd_network_monitor_get_events(monitor); - if (events < 0) - return events; + iov.iov_base = DNS_PACKET_DATA(p); + iov.iov_len = p->size; - r = sd_event_add_io(m->event, &event_source, fd, events, - &manager_network_event_handler, m); - if (r < 0) - return r; + sa.in.sin_addr = srv->address.in; - m->network_monitor = monitor; - m->network_event_source = event_source; - monitor = NULL; - event_source = NULL; + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_name = &sa.sa; + mh.msg_namelen = sizeof(sa.in); - return 0; + if (ifindex > 0) { + struct cmsghdr *cmsg; + struct in_pktinfo *pi; + + zero(control); + + mh.msg_control = control; + mh.msg_controllen = CMSG_LEN(sizeof(struct in_pktinfo)); + + cmsg = CMSG_FIRSTHDR(&mh); + cmsg->cmsg_len = mh.msg_controllen; + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + + pi = (struct in_pktinfo*) CMSG_DATA(cmsg); + pi->ipi_ifindex = ifindex; + } + + return sendmsg_loop(fd, &mh, 0); +} + +int manager_dns_ipv6_send(Manager *m, DnsServer *srv, int ifindex, DnsPacket *p) { + union sockaddr_union sa = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = htobe16(53), + }; + + struct msghdr mh = {}; + struct iovec iov; + uint8_t control[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + int fd; + + assert(m); + assert(srv); + assert(p); + + fd = manager_dns_ipv6_fd(m); + if (fd < 0) + return fd; + + iov.iov_base = DNS_PACKET_DATA(p); + iov.iov_len = p->size; + + sa.in6.sin6_addr = srv->address.in6; + sa.in6.sin6_scope_id = ifindex; + + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_name = &sa.sa; + mh.msg_namelen = sizeof(sa.in6); + + if (ifindex > 0) { + struct cmsghdr *cmsg; + struct in6_pktinfo *pi; + + zero(control); + + mh.msg_control = control; + mh.msg_controllen = CMSG_LEN(sizeof(struct in6_pktinfo)); + + cmsg = CMSG_FIRSTHDR(&mh); + cmsg->cmsg_len = mh.msg_controllen; + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + + pi = (struct in6_pktinfo*) CMSG_DATA(cmsg); + pi->ipi6_ifindex = ifindex; + } + + return sendmsg_loop(fd, &mh, 0); +} + +DnsServer* manager_find_dns_server(Manager *m, unsigned char family, union in_addr_union *in_addr) { + DnsServer *s; + + assert(m); + assert(in_addr); + + LIST_FOREACH(servers, s, m->dns_servers) { + + if (s->family == family && + in_addr_equal(family, &s->address, in_addr)) + return s; + } + + return NULL; +} + +DnsServer *manager_get_dns_server(Manager *m) { + assert(m); + + if (!m->current_dns_server) + m->current_dns_server = m->dns_servers; + + return m->current_dns_server; +} + +void manager_next_dns_server(Manager *m) { + assert(m); + + if (!m->current_dns_server) { + m->current_dns_server = m->dns_servers; + return; + } + + if (!m->current_dns_server) + return; + + if (m->current_dns_server->servers_next) { + m->current_dns_server = m->current_dns_server->servers_next; + return; + } + + m->current_dns_server = m->dns_servers; } diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c index 53f09db73..275f99c92 100644 --- a/src/resolve/resolved.c +++ b/src/resolve/resolved.c @@ -28,7 +28,7 @@ #include "capability.h" int main(int argc, char *argv[]) { - _cleanup_manager_free_ Manager *m = NULL; + _cleanup_(manager_freep) Manager *m = NULL; const char *user = "systemd-resolve"; uid_t uid; gid_t gid; @@ -43,13 +43,13 @@ int main(int argc, char *argv[]) { if (argc != 1) { log_error("This program takes no arguments."); r = -EINVAL; - goto out; + goto finish; } r = get_user_creds(&user, &uid, &gid, NULL, NULL); if (r < 0) { log_error("Cannot resolve user name %s: %s", user, strerror(-r)); - goto out; + goto finish; } /* Always create the directory where resolv.conf will live */ @@ -57,33 +57,31 @@ int main(int argc, char *argv[]) { if (r < 0) { log_error("Could not create runtime directory: %s", strerror(-r)); - goto out; + goto finish; } r = drop_privileges(uid, gid, 0); if (r < 0) - goto out; + goto finish; assert_se(sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1) == 0); r = manager_new(&m); if (r < 0) { log_error("Could not create manager: %s", strerror(-r)); - goto out; + goto finish; } - r = manager_network_monitor_listen(m); - if (r < 0) { - log_error("Could not listen for network events: %s", strerror(-r)); - goto out; - } + r = manager_parse_config_file(m); + if (r < 0) + return r; - /* write out default resolv.conf to avoid a - * dangling symlink */ - r = manager_update_resolv_conf(m); + /* write finish default resolv.conf to avoid a dangling + * symlink */ + r = manager_write_resolv_conf(m); if (r < 0) { log_error("Could not create resolv.conf: %s", strerror(-r)); - goto out; + goto finish; } sd_notify(false, @@ -93,12 +91,11 @@ int main(int argc, char *argv[]) { r = sd_event_loop(m->event); if (r < 0) { log_error("Event loop failed: %s", strerror(-r)); - goto out; + goto finish; } -out: - sd_notify(false, - "STATUS=Shutting down..."); +finish: + sd_notify(false, "STATUS=Shutting down..."); return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/src/resolve/resolved.h b/src/resolve/resolved.h index ad49c63a4..438730be6 100644 --- a/src/resolve/resolved.h +++ b/src/resolve/resolved.h @@ -1,5 +1,7 @@ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ +#pragma once + /*** This file is part of systemd. @@ -19,49 +21,76 @@ along with systemd; If not, see . ***/ -#pragma once - #include "sd-event.h" #include "sd-network.h" - +#include "sd-rtnl.h" #include "util.h" #include "list.h" #include "in-addr-util.h" +#include "hashmap.h" -typedef struct Address Address; typedef struct Manager Manager; -struct Address { - unsigned char family; - - union in_addr_union in_addr; - - LIST_FIELDS(Address, addresses); -}; +#include "resolved-dns-query.h" +#include "resolved-dns-server.h" +#include "resolved-dns-scope.h" struct Manager { sd_event *event; - LIST_HEAD(Address, fallback_dns); - /* network */ - sd_event_source *network_event_source; + Hashmap *links; + + sd_rtnl *rtnl; + sd_event_source *rtnl_event_source; + sd_network_monitor *network_monitor; + sd_event_source *network_event_source; + + /* unicast dns */ + int dns_ipv4_fd; + int dns_ipv6_fd; + + sd_event_source *dns_ipv4_event_source; + sd_event_source *dns_ipv6_event_source; + + Hashmap *dns_query_transactions; + LIST_HEAD(DnsQuery, dns_queries); + + LIST_HEAD(DnsServer, dns_servers); + DnsServer *current_dns_server; + + LIST_HEAD(DnsScope, dns_scopes); + DnsScope *unicast_scope; + + /* dbus */ + sd_bus *bus; + sd_event_source *bus_retry_event_source; }; /* Manager */ int manager_new(Manager **ret); -void manager_free(Manager *m); +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, unsigned char family, union in_addr_union *in_addr); +DnsServer *manager_get_dns_server(Manager *m); +void manager_next_dns_server(Manager *m); -int manager_update_resolv_conf(Manager *m); -int manager_network_monitor_listen(Manager *m); +int manager_dns_ipv4_fd(Manager *m); +int manager_dns_ipv4_send(Manager *m, DnsServer *srv, int ifindex, DnsPacket *p); +int manager_dns_ipv4_recv(Manager *m, DnsPacket **ret); + +int manager_dns_ipv6_fd(Manager *m); +int manager_dns_ipv6_send(Manager *m, DnsServer *srv, int ifindex, DnsPacket *p); +int manager_dns_ipv6_recv(Manager *m, DnsPacket **ret); + +int manager_connect_bus(Manager *m); DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); -#define _cleanup_manager_free_ _cleanup_(manager_freep) 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); +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); diff --git a/src/resolve/test-dns-domain.c b/src/resolve/test-dns-domain.c new file mode 100644 index 000000000..bd53402be --- /dev/null +++ b/src/resolve/test-dns-domain.c @@ -0,0 +1,173 @@ +/*-*- 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 . + ***/ + +#include "log.h" +#include "resolved-dns-domain.h" + +static void test_dns_label_unescape_one(const char *what, const char *expect, size_t buffer_sz, int ret) { + char buffer[buffer_sz]; + int r; + + r = dns_label_unescape(&what, buffer, buffer_sz); + assert_se(r == ret); + + if (r < 0) + return; + + assert_se(streq(buffer, expect)); +} + +static void test_dns_label_unescape(void) { + test_dns_label_unescape_one("hallo", "hallo", 6, 5); + test_dns_label_unescape_one("hallo", "hallo", 4, -ENOSPC); + test_dns_label_unescape_one("", "", 10, 0); + test_dns_label_unescape_one("hallo\\.foobar", "hallo.foobar", 20, 12); + test_dns_label_unescape_one("hallo.foobar", "hallo", 10, 5); + test_dns_label_unescape_one("hallo\n.foobar", "hallo", 20, -EINVAL); + test_dns_label_unescape_one("hallo\\", "hallo", 20, -EINVAL); + test_dns_label_unescape_one("hallo\\032 ", "hallo ", 20, 7); + test_dns_label_unescape_one(".", "", 20, 0); + test_dns_label_unescape_one("..", "", 20, -EINVAL); + test_dns_label_unescape_one(".foobar", "", 20, -EINVAL); + test_dns_label_unescape_one("foobar.", "foobar", 20, 6); +} + +static void test_dns_label_escape_one(const char *what, size_t l, const char *expect, int ret) { + _cleanup_free_ char *t = NULL; + int r; + + r = dns_label_escape(what, l, &t); + assert(r == ret); + + if (r < 0) + return; + + assert_se(streq_ptr(expect, t)); +} + +static void test_dns_label_escape(void) { + test_dns_label_escape_one("", 0, "", 0); + test_dns_label_escape_one("hallo", 5, "hallo", 5); + test_dns_label_escape_one("hallo", 6, NULL, -EINVAL); + test_dns_label_escape_one("hallo hallo.foobar,waldi", 24, "hallo\\032hallo\\.foobar\\044waldi", 31); +} + +static void test_dns_name_normalize_one(const char *what, const char *expect, int ret) { + _cleanup_free_ char *t = NULL; + int r; + + r = dns_name_normalize(what, &t); + assert_se(r == ret); + + if (r < 0) + return; + + assert_se(streq_ptr(expect, t)); +} + +static void test_dns_name_normalize(void) { + test_dns_name_normalize_one("", "", 0); + test_dns_name_normalize_one("f", "f", 0); + test_dns_name_normalize_one("f.waldi", "f.waldi", 0); + test_dns_name_normalize_one("f \\032.waldi", "f\\032\\032.waldi", 0); + test_dns_name_normalize_one("\\000", NULL, -EINVAL); + test_dns_name_normalize_one("..", NULL, -EINVAL); + test_dns_name_normalize_one(".foobar", NULL, -EINVAL); + test_dns_name_normalize_one("foobar.", "foobar", 0); + test_dns_name_normalize_one(".", "", 0); +} + +static void test_dns_name_equal_one(const char *a, const char *b, int ret) { + int r; + + r = dns_name_equal(a, b); + assert_se(r == ret); + + r = dns_name_equal(b, a); + assert_se(r == ret); +} + +static void test_dns_name_equal(void) { + test_dns_name_equal_one("", "", true); + test_dns_name_equal_one("x", "x", true); + test_dns_name_equal_one("x", "x.", true); + test_dns_name_equal_one("abc.def", "abc.def", true); + test_dns_name_equal_one("abc.def", "ABC.def", true); + test_dns_name_equal_one("abc.def", "CBA.def", false); + test_dns_name_equal_one("", "xxx", false); + test_dns_name_equal_one("ab", "a", false); + test_dns_name_equal_one("\\000", "xxxx", -EINVAL); + test_dns_name_equal_one(".", "", true); + test_dns_name_equal_one(".", ".", true); + test_dns_name_equal_one("..", "..", -EINVAL); +} + +static void test_dns_name_endswith_one(const char *a, const char *b, int ret) { + assert_se(dns_name_endswith(a, b) == ret); +} + +static void test_dns_name_endswith(void) { + test_dns_name_endswith_one("", "", true); + test_dns_name_endswith_one("", "xxx", false); + test_dns_name_endswith_one("xxx", "", true); + test_dns_name_endswith_one("x", "x", true); + test_dns_name_endswith_one("x", "y", false); + test_dns_name_endswith_one("x.y", "y", true); + test_dns_name_endswith_one("x.y", "Y", true); + test_dns_name_endswith_one("x.y", "x", false); + test_dns_name_endswith_one("x.y.z", "Z", true); + test_dns_name_endswith_one("x.y.z", "y.Z", true); + test_dns_name_endswith_one("x.y.z", "x.y.Z", true); + test_dns_name_endswith_one("x.y.z", "waldo", false); + test_dns_name_endswith_one("x.y.z.u.v.w", "y.z", false); + test_dns_name_endswith_one("x.y.z.u.v.w", "u.v.w", true); + test_dns_name_endswith_one("x.y\001.z", "waldo", -EINVAL); +} + +static void test_dns_name_root(void) { + assert_se(dns_name_root("") == true); + assert_se(dns_name_root(".") == true); + assert_se(dns_name_root("xxx") == false); + assert_se(dns_name_root("xxx.") == false); + assert_se(dns_name_root("..") == -EINVAL); +} + +static void test_dns_name_single_label(void) { + assert_se(dns_name_single_label("") == false); + assert_se(dns_name_single_label(".") == false); + assert_se(dns_name_single_label("..") == -EINVAL); + assert_se(dns_name_single_label("x") == true); + assert_se(dns_name_single_label("x.") == true); + assert_se(dns_name_single_label("xx.yy") == false); +} + +int main(int argc, char *argv[]) { + + test_dns_label_unescape(); + test_dns_label_escape(); + test_dns_name_normalize(); + test_dns_name_equal(); + test_dns_name_endswith(); + test_dns_name_root(); + test_dns_name_single_label(); + + return 0; +} diff --git a/src/shared/bus-errors.h b/src/shared/bus-errors.h index 48a2b4ef1..388b42b4f 100644 --- a/src/shared/bus-errors.h +++ b/src/shared/bus-errors.h @@ -59,3 +59,7 @@ #define BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED "org.freedesktop.timedate1.AutomaticTimeSyncEnabled" #define BUS_ERROR_NO_SUCH_PROCESS "org.freedesktop.systemd1.NoSuchProcess" + +#define BUS_ERROR_NO_NAME_SERVERS "org.freedesktop.resolve1.NoNameServers" +#define BUS_ERROR_INVALID_REPLY "org.freedesktop.resolve1.InvalidReply" +#define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError." diff --git a/src/shared/in-addr-util.c b/src/shared/in-addr-util.c index 98d2446e5..6ece85e37 100644 --- a/src/shared/in-addr-util.c +++ b/src/shared/in-addr-util.c @@ -209,3 +209,25 @@ int in_addr_from_string(unsigned family, const char *s, union in_addr_union *ret return 0; } + +int in_addr_from_string_auto(const char *s, unsigned *family, union in_addr_union *ret) { + int r; + + assert(s); + assert(family); + assert(ret); + + r = in_addr_from_string(AF_INET, s, ret); + if (r >= 0) { + *family = AF_INET; + return 0; + } + + r = in_addr_from_string(AF_INET6, s, ret); + if (r >= 0) { + *family = AF_INET6; + return 0; + } + + return -EINVAL; +} diff --git a/src/shared/in-addr-util.h b/src/shared/in-addr-util.h index ae3aa9021..108f1f3ac 100644 --- a/src/shared/in-addr-util.h +++ b/src/shared/in-addr-util.h @@ -37,6 +37,7 @@ int in_addr_prefix_intersect(unsigned family, const union in_addr_union *a, unsi int in_addr_prefix_next(unsigned family, union in_addr_union *u, unsigned prefixlen); int in_addr_to_string(unsigned family, const union in_addr_union *u, char **ret); int in_addr_from_string(unsigned family, const char *s, union in_addr_union *ret); +int in_addr_from_string_auto(const char *s, unsigned *family, union in_addr_union *ret); static inline size_t PROTO_ADDRESS_SIZE(int proto) { assert(proto == AF_INET || proto == AF_INET6); diff --git a/src/shared/macro.h b/src/shared/macro.h index 70c5fb50a..5619c32e4 100644 --- a/src/shared/macro.h +++ b/src/shared/macro.h @@ -241,6 +241,9 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) { #define PTR_TO_UINT64(p) ((uint64_t) ((uintptr_t) (p))) #define UINT64_TO_PTR(u) ((void *) ((uintptr_t) (u))) +#define PTR_TO_SIZE(p) ((size_t) ((uintptr_t) (p))) +#define SIZE_TO_PTR(u) ((void *) ((uintptr_t) (u))) + #define memzero(x,l) (memset((x), 0, (l))) #define zero(x) (memzero(&(x), sizeof(x))) diff --git a/src/systemd/sd-dhcp-lease.h b/src/systemd/sd-dhcp-lease.h index c15744d6c..6e8cefff4 100644 --- a/src/systemd/sd-dhcp-lease.h +++ b/src/systemd/sd-dhcp-lease.h @@ -31,6 +31,7 @@ struct sd_dhcp_route; sd_dhcp_lease *sd_dhcp_lease_ref(sd_dhcp_lease *lease); sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease); + int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr); int sd_dhcp_lease_get_lifetime(sd_dhcp_lease *lease, uint32_t *lifetime); int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr); @@ -44,4 +45,5 @@ int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname); int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname); int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path); int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, struct sd_dhcp_route **routes, size_t *routes_size); + #endif -- 2.30.2