summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/analyze/analyze-verify.c60
-rw-r--r--src/basic/bitmap.c12
-rw-r--r--src/basic/capability-util.c71
-rw-r--r--src/basic/capability-util.h15
-rw-r--r--src/basic/cgroup-util.c3
-rw-r--r--src/basic/conf-files.c22
-rw-r--r--src/basic/fd-util.h3
-rw-r--r--src/basic/hash-funcs.c83
-rw-r--r--src/basic/hash-funcs.h67
-rw-r--r--src/basic/hashmap.c61
-rw-r--r--src/basic/hashmap.h43
-rw-r--r--src/basic/log.c4
-rw-r--r--src/basic/macro.h53
-rw-r--r--src/basic/missing.h20
-rw-r--r--src/basic/signal-util.c5
-rw-r--r--src/basic/siphash24.h2
-rw-r--r--src/basic/socket-util.c11
-rw-r--r--src/basic/string-table.h18
-rw-r--r--src/basic/string-util.c23
-rw-r--r--src/basic/string-util.h4
-rw-r--r--src/bootchart/svg.c3
-rw-r--r--src/cgtop/cgtop.c5
-rw-r--r--src/core/dbus-execute.c39
-rw-r--r--src/core/dbus-manager.c21
-rw-r--r--src/core/dbus.c66
-rw-r--r--src/core/dbus.h2
-rw-r--r--src/core/execute.c109
-rw-r--r--src/core/execute.h4
-rw-r--r--src/core/job.c3
-rw-r--r--src/core/load-fragment-gperf.gperf.m43
-rw-r--r--src/core/load-fragment.c29
-rw-r--r--src/core/load-fragment.h2
-rw-r--r--src/core/main.c10
-rw-r--r--src/core/manager.c16
-rw-r--r--src/core/mount-setup.c15
-rw-r--r--src/core/service.c14
-rw-r--r--src/core/service.h1
-rw-r--r--src/core/smack-setup.c101
-rw-r--r--src/core/socket.c24
-rw-r--r--src/core/transaction.c2
-rw-r--r--src/core/unit.c7
-rw-r--r--src/hostname/hostnamed.c2
-rw-r--r--src/import/import-common.c4
-rw-r--r--src/import/importd.c123
-rw-r--r--src/import/org.freedesktop.import1.conf4
-rw-r--r--src/import/pull-dkr.c1346
-rw-r--r--src/import/pull-dkr.h38
-rw-r--r--src/import/pull.c128
-rw-r--r--src/journal-remote/journal-gatewayd.c8
-rw-r--r--src/journal-remote/journal-remote.c1
-rw-r--r--src/journal-remote/journal-remote.conf.in1
-rw-r--r--src/journal/compress.c31
-rw-r--r--src/journal/compress.h13
-rw-r--r--src/journal/journal-file.c5
-rw-r--r--src/journal/journald-native.c4
-rw-r--r--src/journal/journald-stream.c2
-rw-r--r--src/journal/journald-syslog.c10
-rw-r--r--src/journal/mmap-cache.c5
-rw-r--r--src/journal/sd-journal.c8
-rw-r--r--src/journal/test-compress-benchmark.c7
-rw-r--r--src/journal/test-compress.c121
-rw-r--r--src/libsystemd-network/sd-dhcp-lease.c3
-rw-r--r--src/libsystemd-network/sd-lldp.c88
-rw-r--r--src/libsystemd/sd-bus/bus-common-errors.c4
-rw-r--r--src/libsystemd/sd-bus/bus-common-errors.h5
-rw-r--r--src/libsystemd/sd-bus/bus-kernel.c7
-rw-r--r--src/libsystemd/sd-bus/test-bus-cleanup.c10
-rw-r--r--src/libsystemd/sd-bus/test-bus-marshal.c2
-rw-r--r--src/libsystemd/sd-event/sd-event.c79
-rw-r--r--src/libsystemd/sd-netlink/netlink-socket.c2
-rw-r--r--src/libsystemd/sd-network/sd-network.c159
-rw-r--r--src/libsystemd/sd-resolve/test-resolve.c8
-rw-r--r--src/login/logind-dbus.c6
-rw-r--r--src/login/logind-seat.c3
-rw-r--r--src/machine/machine-dbus.c2
-rw-r--r--src/machine/machinectl.c90
-rw-r--r--src/network/networkctl.c4
-rw-r--r--src/network/networkd-link.c25
-rw-r--r--src/network/networkd-network-gperf.gperf5
-rw-r--r--src/network/networkd-network.c57
-rw-r--r--src/network/networkd-network.h5
-rw-r--r--src/network/networkd-util.c51
-rw-r--r--src/network/networkd-util.h12
-rw-r--r--src/nspawn/nspawn-cgroup.c1
-rw-r--r--src/nspawn/nspawn-setuid.c4
-rw-r--r--src/nspawn/nspawn.c2
-rw-r--r--src/resolve-host/resolve-host.c424
-rw-r--r--src/resolve/RFCs59
-rw-r--r--src/resolve/dns-type.c141
-rw-r--r--src/resolve/dns-type.h29
-rw-r--r--src/resolve/resolved-bus.c132
-rw-r--r--src/resolve/resolved-conf.c69
-rw-r--r--src/resolve/resolved-conf.h1
-rw-r--r--src/resolve/resolved-dns-answer.c627
-rw-r--r--src/resolve/resolved-dns-answer.h78
-rw-r--r--src/resolve/resolved-dns-cache.c578
-rw-r--r--src/resolve/resolved-dns-cache.h6
-rw-r--r--src/resolve/resolved-dns-dnssec.c1332
-rw-r--r--src/resolve/resolved-dns-dnssec.h72
-rw-r--r--src/resolve/resolved-dns-packet.c318
-rw-r--r--src/resolve/resolved-dns-packet.h33
-rw-r--r--src/resolve/resolved-dns-query.c150
-rw-r--r--src/resolve/resolved-dns-query.h11
-rw-r--r--src/resolve/resolved-dns-question.c2
-rw-r--r--src/resolve/resolved-dns-rr.c330
-rw-r--r--src/resolve/resolved-dns-rr.h90
-rw-r--r--src/resolve/resolved-dns-scope.c151
-rw-r--r--src/resolve/resolved-dns-scope.h6
-rw-r--r--src/resolve/resolved-dns-server.c254
-rw-r--r--src/resolve/resolved-dns-server.h38
-rw-r--r--src/resolve/resolved-dns-stream.c9
-rw-r--r--src/resolve/resolved-dns-transaction.c2157
-rw-r--r--src/resolve/resolved-dns-transaction.h64
-rw-r--r--src/resolve/resolved-dns-trust-anchor.c664
-rw-r--r--src/resolve/resolved-dns-trust-anchor.h10
-rw-r--r--src/resolve/resolved-dns-zone.c41
-rw-r--r--src/resolve/resolved-dns-zone.h2
-rw-r--r--src/resolve/resolved-gperf.gperf11
-rw-r--r--src/resolve/resolved-link.c183
-rw-r--r--src/resolve/resolved-link.h7
-rw-r--r--src/resolve/resolved-llmnr.c22
-rw-r--r--src/resolve/resolved-manager.c23
-rw-r--r--src/resolve/resolved-manager.h21
-rw-r--r--src/resolve/resolved-mdns.c23
-rw-r--r--src/resolve/resolved-resolv-conf.c12
-rw-r--r--src/resolve/resolved.c6
-rw-r--r--src/resolve/resolved.conf.in1
-rw-r--r--src/resolve/test-dnssec.c163
-rw-r--r--src/shared/bus-util.c11
-rw-r--r--src/shared/dns-domain.c156
-rw-r--r--src/shared/dns-domain.h12
-rw-r--r--src/shared/generator.c5
-rw-r--r--src/shared/gpt.h7
-rw-r--r--src/shared/import-util.c52
-rw-r--r--src/shared/import-util.h6
-rw-r--r--src/shared/logs-show.c3
-rw-r--r--src/shared/resolve-util.c41
-rw-r--r--src/shared/resolve-util.h62
-rw-r--r--src/shared/switch-root.c3
-rw-r--r--src/systemd/sd-messages.h3
-rw-r--r--src/systemd/sd-network.h21
-rw-r--r--src/test/test-capability.c68
-rw-r--r--src/test/test-dns-domain.c98
-rw-r--r--src/test/test-execute.c16
-rw-r--r--src/test/test-libudev.c3
-rw-r--r--src/test/test-rlimit-util.c4
-rw-r--r--src/test/test-unit-file.c41
-rw-r--r--src/udev/collect/collect.c3
-rw-r--r--src/udev/udev-builtin-input_id.c14
-rw-r--r--src/udev/udev-builtin-net_id.c15
-rw-r--r--src/udev/udev-node.c11
-rw-r--r--src/udev/udev-watch.c7
-rw-r--r--src/vconsole/vconsole-setup.c5
153 files changed, 8805 insertions, 3763 deletions
diff --git a/src/analyze/analyze-verify.c b/src/analyze/analyze-verify.c
index 6e460697db..3c9766da04 100644
--- a/src/analyze/analyze-verify.c
+++ b/src/analyze/analyze-verify.c
@@ -30,6 +30,47 @@
#include "pager.h"
#include "path-util.h"
#include "strv.h"
+#include "unit-name.h"
+
+static int prepare_filename(const char *filename, char **ret) {
+ int r;
+ const char *name;
+ _cleanup_free_ char *abspath = NULL;
+ _cleanup_free_ char *dir = NULL;
+ _cleanup_free_ char *with_instance = NULL;
+ char *c;
+
+ assert(filename);
+ assert(ret);
+
+ r = path_make_absolute_cwd(filename, &abspath);
+ if (r < 0)
+ return r;
+
+ name = basename(abspath);
+ if (!unit_name_is_valid(name, UNIT_NAME_ANY))
+ return -EINVAL;
+
+ if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) {
+ r = unit_name_replace_instance(name, "i", &with_instance);
+ if (r < 0)
+ return r;
+ }
+
+ dir = dirname_malloc(abspath);
+ if (!dir)
+ return -ENOMEM;
+
+ if (with_instance)
+ c = path_join(NULL, dir, with_instance);
+ else
+ c = path_join(NULL, dir, name);
+ if (!c)
+ return -ENOMEM;
+
+ *ret = c;
+ return 0;
+}
static int generate_path(char **var, char **filenames) {
char **filename;
@@ -233,18 +274,19 @@ int verify_units(char **filenames, ManagerRunningAs running_as, bool check_man)
log_debug("Loading remaining units from the command line...");
STRV_FOREACH(filename, filenames) {
- char fname[UNIT_NAME_MAX + 2 + 1] = "./";
+ _cleanup_free_ char *prepared = NULL;
log_debug("Handling %s...", *filename);
- /* manager_load_unit does not like pure basenames, so prepend
- * the local directory, but only for valid names. manager_load_unit
- * will print the error for other ones. */
- if (!strchr(*filename, '/') && strlen(*filename) <= UNIT_NAME_MAX) {
- strncat(fname + 2, *filename, UNIT_NAME_MAX);
- k = manager_load_unit(m, NULL, fname, &err, &units[count]);
- } else
- k = manager_load_unit(m, NULL, *filename, &err, &units[count]);
+ k = prepare_filename(*filename, &prepared);
+ if (k < 0) {
+ log_error_errno(k, "Failed to prepare filename %s: %m", *filename);
+ if (r == 0)
+ r = k;
+ continue;
+ }
+
+ k = manager_load_unit(m, NULL, prepared, &err, &units[count]);
if (k < 0) {
log_error_errno(k, "Failed to load %s: %m", *filename);
if (r == 0)
diff --git a/src/basic/bitmap.c b/src/basic/bitmap.c
index 95f59e400a..50078822a7 100644
--- a/src/basic/bitmap.c
+++ b/src/basic/bitmap.c
@@ -140,7 +140,8 @@ bool bitmap_isset(Bitmap *b, unsigned n) {
bool bitmap_isclear(Bitmap *b) {
unsigned i;
- assert(b);
+ if (!b)
+ return true;
for (i = 0; i < b->n_bitmaps; i++)
if (b->bitmaps[i] != 0)
@@ -150,7 +151,9 @@ bool bitmap_isclear(Bitmap *b) {
}
void bitmap_clear(Bitmap *b) {
- assert(b);
+
+ if (!b)
+ return;
b->bitmaps = mfree(b->bitmaps);
b->n_bitmaps = 0;
@@ -197,7 +200,10 @@ bool bitmap_equal(Bitmap *a, Bitmap *b) {
Bitmap *c;
unsigned i;
- if (!a ^ !b)
+ if (a == b)
+ return true;
+
+ if (!a != !b)
return false;
if (!a)
diff --git a/src/basic/capability-util.c b/src/basic/capability-util.c
index fef722b6f2..49c2d61afe 100644
--- a/src/basic/capability-util.c
+++ b/src/basic/capability-util.c
@@ -96,7 +96,62 @@ unsigned long cap_last_cap(void) {
return p;
}
-int capability_bounding_set_drop(uint64_t drop, bool right_now) {
+int capability_update_inherited_set(cap_t caps, uint64_t set) {
+ unsigned long i;
+
+ /* Add capabilities in the set to the inherited caps. Do not apply
+ * them yet. */
+
+ for (i = 0; i < cap_last_cap(); i++) {
+
+ if (set & (UINT64_C(1) << i)) {
+ cap_value_t v;
+
+ v = (cap_value_t) i;
+
+ /* Make the capability inheritable. */
+ if (cap_set_flag(caps, CAP_INHERITABLE, 1, &v, CAP_SET) < 0)
+ return -errno;
+ }
+ }
+
+ return 0;
+}
+
+int capability_ambient_set_apply(uint64_t set, bool also_inherit) {
+ unsigned long i;
+ _cleanup_cap_free_ cap_t caps = NULL;
+
+ /* Add the capabilities to the ambient set. */
+
+ if (also_inherit) {
+ int r;
+ caps = cap_get_proc();
+ if (!caps)
+ return -errno;
+
+ r = capability_update_inherited_set(caps, set);
+ if (r < 0)
+ return -errno;
+
+ if (cap_set_proc(caps) < 0)
+ return -errno;
+ }
+
+ for (i = 0; i < cap_last_cap(); i++) {
+
+ if (set & (UINT64_C(1) << i)) {
+
+ /* Add the capability to the ambient set. */
+ if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, i, 0, 0) < 0)
+ return -errno;
+ }
+ }
+
+ return 0;
+}
+
+int capability_bounding_set_drop(uint64_t keep, bool right_now) {
_cleanup_cap_free_ cap_t after_cap = NULL;
cap_flag_value_t fv;
unsigned long i;
@@ -137,7 +192,7 @@ int capability_bounding_set_drop(uint64_t drop, bool right_now) {
for (i = 0; i <= cap_last_cap(); i++) {
- if (drop & ((uint64_t) 1ULL << (uint64_t) i)) {
+ if (!(keep & (UINT64_C(1) << i))) {
cap_value_t v;
/* Drop it from the bounding set */
@@ -176,7 +231,7 @@ finish:
return r;
}
-static int drop_from_file(const char *fn, uint64_t drop) {
+static int drop_from_file(const char *fn, uint64_t keep) {
int r, k;
uint32_t hi, lo;
uint64_t current, after;
@@ -196,7 +251,7 @@ static int drop_from_file(const char *fn, uint64_t drop) {
return -EIO;
current = (uint64_t) lo | ((uint64_t) hi << 32ULL);
- after = current & ~drop;
+ after = current & keep;
if (current == after)
return 0;
@@ -213,14 +268,14 @@ static int drop_from_file(const char *fn, uint64_t drop) {
return r;
}
-int capability_bounding_set_drop_usermode(uint64_t drop) {
+int capability_bounding_set_drop_usermode(uint64_t keep) {
int r;
- r = drop_from_file("/proc/sys/kernel/usermodehelper/inheritable", drop);
+ r = drop_from_file("/proc/sys/kernel/usermodehelper/inheritable", keep);
if (r < 0)
return r;
- r = drop_from_file("/proc/sys/kernel/usermodehelper/bset", drop);
+ r = drop_from_file("/proc/sys/kernel/usermodehelper/bset", keep);
if (r < 0)
return r;
@@ -257,7 +312,7 @@ int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities) {
return log_error_errno(errno, "Failed to disable keep capabilities flag: %m");
/* Drop all caps from the bounding set, except the ones we want */
- r = capability_bounding_set_drop(~keep_capabilities, true);
+ r = capability_bounding_set_drop(keep_capabilities, true);
if (r < 0)
return log_error_errno(r, "Failed to drop capabilities: %m");
diff --git a/src/basic/capability-util.h b/src/basic/capability-util.h
index 6bbf7318fd..be41475441 100644
--- a/src/basic/capability-util.h
+++ b/src/basic/capability-util.h
@@ -29,10 +29,15 @@
#include "macro.h"
#include "util.h"
+#define CAP_ALL (uint64_t) -1
+
unsigned long cap_last_cap(void);
int have_effective_cap(int value);
-int capability_bounding_set_drop(uint64_t drop, bool right_now);
-int capability_bounding_set_drop_usermode(uint64_t drop);
+int capability_bounding_set_drop(uint64_t keep, bool right_now);
+int capability_bounding_set_drop_usermode(uint64_t keep);
+
+int capability_ambient_set_apply(uint64_t set, bool also_inherit);
+int capability_update_inherited_set(cap_t caps, uint64_t ambient_set);
int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities);
@@ -46,3 +51,9 @@ static inline void cap_free_charpp(char **p) {
cap_free(*p);
}
#define _cleanup_cap_free_charp_ _cleanup_(cap_free_charpp)
+
+static inline bool cap_test_all(uint64_t caps) {
+ uint64_t m;
+ m = (UINT64_C(1) << (cap_last_cap() + 1)) - 1;
+ return (caps & m) == m;
+}
diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c
index 639f9f3db1..3945d37c8d 100644
--- a/src/basic/cgroup-util.c
+++ b/src/basic/cgroup-util.c
@@ -53,6 +53,7 @@
#include "set.h"
#include "special.h"
#include "stat-util.h"
+#include "stdio-util.h"
#include "string-table.h"
#include "string-util.h"
#include "unit-name.h"
@@ -716,7 +717,7 @@ int cg_attach(const char *controller, const char *path, pid_t pid) {
if (pid == 0)
pid = getpid();
- snprintf(c, sizeof(c), PID_FMT"\n", pid);
+ xsprintf(c, PID_FMT "\n", pid);
return write_string_file(fs, c, 0);
}
diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c
index 75dad228e3..5854caeb51 100644
--- a/src/basic/conf-files.c
+++ b/src/basic/conf-files.c
@@ -41,6 +41,7 @@
static int files_add(Hashmap *h, const char *root, const char *path, const char *suffix) {
_cleanup_closedir_ DIR *dir = NULL;
const char *dirpath;
+ struct dirent *de;
int r;
assert(path);
@@ -55,18 +56,9 @@ static int files_add(Hashmap *h, const char *root, const char *path, const char
return -errno;
}
- for (;;) {
- struct dirent *de;
+ FOREACH_DIRENT(de, dir, return -errno) {
char *p;
- errno = 0;
- de = readdir(dir);
- if (!de && errno != 0)
- return -errno;
-
- if (!de)
- break;
-
if (!dirent_is_file_with_suffix(de, suffix))
continue;
@@ -116,17 +108,15 @@ static int conf_files_list_strv_internal(char ***strv, const char *suffix, const
STRV_FOREACH(p, dirs) {
r = files_add(fh, root, *p, suffix);
- if (r == -ENOMEM) {
+ if (r == -ENOMEM)
return r;
- } else if (r < 0)
- log_debug_errno(r, "Failed to search for files in %s: %m",
- *p);
+ if (r < 0)
+ log_debug_errno(r, "Failed to search for files in %s, ignoring: %m", *p);
}
files = hashmap_get_strv(fh);
- if (files == NULL) {
+ if (!files)
return -ENOMEM;
- }
qsort_safe(files, hashmap_size(fh), sizeof(char *), base_cmp);
*strv = files;
diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h
index 5ce1592eeb..973413ff42 100644
--- a/src/basic/fd-util.h
+++ b/src/basic/fd-util.h
@@ -73,3 +73,6 @@ int same_fd(int a, int b);
void cmsg_close_all(struct msghdr *mh);
bool fdname_is_valid(const char *s);
+
+#define ERRNO_IS_DISCONNECT(r) \
+ IN_SET(r, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE)
diff --git a/src/basic/hash-funcs.c b/src/basic/hash-funcs.c
new file mode 100644
index 0000000000..d4affaffee
--- /dev/null
+++ b/src/basic/hash-funcs.c
@@ -0,0 +1,83 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+ Copyright 2014 Michal Schmidt
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "hash-funcs.h"
+
+void string_hash_func(const void *p, struct siphash *state) {
+ siphash24_compress(p, strlen(p) + 1, state);
+}
+
+int string_compare_func(const void *a, const void *b) {
+ return strcmp(a, b);
+}
+
+const struct hash_ops string_hash_ops = {
+ .hash = string_hash_func,
+ .compare = string_compare_func
+};
+
+void trivial_hash_func(const void *p, struct siphash *state) {
+ siphash24_compress(&p, sizeof(p), state);
+}
+
+int trivial_compare_func(const void *a, const void *b) {
+ return a < b ? -1 : (a > b ? 1 : 0);
+}
+
+const struct hash_ops trivial_hash_ops = {
+ .hash = trivial_hash_func,
+ .compare = trivial_compare_func
+};
+
+void uint64_hash_func(const void *p, struct siphash *state) {
+ siphash24_compress(p, sizeof(uint64_t), state);
+}
+
+int uint64_compare_func(const void *_a, const void *_b) {
+ uint64_t a, b;
+ a = *(const uint64_t*) _a;
+ b = *(const uint64_t*) _b;
+ return a < b ? -1 : (a > b ? 1 : 0);
+}
+
+const struct hash_ops uint64_hash_ops = {
+ .hash = uint64_hash_func,
+ .compare = uint64_compare_func
+};
+
+#if SIZEOF_DEV_T != 8
+void devt_hash_func(const void *p, struct siphash *state) {
+ siphash24_compress(p, sizeof(dev_t), state);
+}
+
+int devt_compare_func(const void *_a, const void *_b) {
+ dev_t a, b;
+ a = *(const dev_t*) _a;
+ b = *(const dev_t*) _b;
+ return a < b ? -1 : (a > b ? 1 : 0);
+}
+
+const struct hash_ops devt_hash_ops = {
+ .hash = devt_hash_func,
+ .compare = devt_compare_func
+};
+#endif
diff --git a/src/basic/hash-funcs.h b/src/basic/hash-funcs.h
new file mode 100644
index 0000000000..c640eaf4d1
--- /dev/null
+++ b/src/basic/hash-funcs.h
@@ -0,0 +1,67 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+ Copyright 2014 Michal Schmidt
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "macro.h"
+#include "siphash24.h"
+
+typedef void (*hash_func_t)(const void *p, struct siphash *state);
+typedef int (*compare_func_t)(const void *a, const void *b);
+
+struct hash_ops {
+ hash_func_t hash;
+ compare_func_t compare;
+};
+
+void string_hash_func(const void *p, struct siphash *state);
+int string_compare_func(const void *a, const void *b) _pure_;
+extern const struct hash_ops string_hash_ops;
+
+/* This will compare the passed pointers directly, and will not
+ * dereference them. This is hence not useful for strings or
+ * suchlike. */
+void trivial_hash_func(const void *p, struct siphash *state);
+int trivial_compare_func(const void *a, const void *b) _const_;
+extern const struct hash_ops trivial_hash_ops;
+
+/* 32bit values we can always just embed in the pointer itself, but
+ * in order to support 32bit archs we need store 64bit values
+ * indirectly, since they don't fit in a pointer. */
+void uint64_hash_func(const void *p, struct siphash *state);
+int uint64_compare_func(const void *a, const void *b) _pure_;
+extern const struct hash_ops uint64_hash_ops;
+
+/* On some archs dev_t is 32bit, and on others 64bit. And sometimes
+ * it's 64bit on 32bit archs, and sometimes 32bit on 64bit archs. Yuck! */
+#if SIZEOF_DEV_T != 8
+void devt_hash_func(const void *p, struct siphash *state) _pure_;
+int devt_compare_func(const void *a, const void *b) _pure_;
+extern const struct hash_ops devt_hash_ops = {
+ .hash = devt_hash_func,
+ .compare = devt_compare_func
+};
+#else
+#define devt_hash_func uint64_hash_func
+#define devt_compare_func uint64_compare_func
+#define devt_hash_ops uint64_hash_ops
+#endif
diff --git a/src/basic/hashmap.c b/src/basic/hashmap.c
index b3954e3223..dcd8ae412d 100644
--- a/src/basic/hashmap.c
+++ b/src/basic/hashmap.c
@@ -37,6 +37,7 @@
#include "util.h"
#ifdef ENABLE_DEBUG_HASHMAP
+#include <pthread.h>
#include "list.h"
#endif
@@ -279,66 +280,6 @@ static const struct hashmap_type_info hashmap_type_info[_HASHMAP_TYPE_MAX] = {
},
};
-void string_hash_func(const void *p, struct siphash *state) {
- siphash24_compress(p, strlen(p) + 1, state);
-}
-
-int string_compare_func(const void *a, const void *b) {
- return strcmp(a, b);
-}
-
-const struct hash_ops string_hash_ops = {
- .hash = string_hash_func,
- .compare = string_compare_func
-};
-
-void trivial_hash_func(const void *p, struct siphash *state) {
- siphash24_compress(&p, sizeof(p), state);
-}
-
-int trivial_compare_func(const void *a, const void *b) {
- return a < b ? -1 : (a > b ? 1 : 0);
-}
-
-const struct hash_ops trivial_hash_ops = {
- .hash = trivial_hash_func,
- .compare = trivial_compare_func
-};
-
-void uint64_hash_func(const void *p, struct siphash *state) {
- siphash24_compress(p, sizeof(uint64_t), state);
-}
-
-int uint64_compare_func(const void *_a, const void *_b) {
- uint64_t a, b;
- a = *(const uint64_t*) _a;
- b = *(const uint64_t*) _b;
- return a < b ? -1 : (a > b ? 1 : 0);
-}
-
-const struct hash_ops uint64_hash_ops = {
- .hash = uint64_hash_func,
- .compare = uint64_compare_func
-};
-
-#if SIZEOF_DEV_T != 8
-void devt_hash_func(const void *p, struct siphash *state) {
- siphash24_compress(p, sizeof(dev_t), state);
-}
-
-int devt_compare_func(const void *_a, const void *_b) {
- dev_t a, b;
- a = *(const dev_t*) _a;
- b = *(const dev_t*) _b;
- return a < b ? -1 : (a > b ? 1 : 0);
-}
-
-const struct hash_ops devt_hash_ops = {
- .hash = devt_hash_func,
- .compare = devt_compare_func
-};
-#endif
-
static unsigned n_buckets(HashmapBase *h) {
return h->has_indirect ? h->indirect.n_buckets
: hashmap_type_info[h->type].n_direct_buckets;
diff --git a/src/basic/hashmap.h b/src/basic/hashmap.h
index 708811124b..fdba9c61ff 100644
--- a/src/basic/hashmap.h
+++ b/src/basic/hashmap.h
@@ -26,8 +26,8 @@
#include <stdbool.h>
#include <stddef.h>
+#include "hash-funcs.h"
#include "macro.h"
-#include "siphash24.h"
#include "util.h"
/*
@@ -70,47 +70,6 @@ typedef struct {
#define _IDX_ITERATOR_FIRST (UINT_MAX - 1)
#define ITERATOR_FIRST ((Iterator) { .idx = _IDX_ITERATOR_FIRST, .next_key = NULL })
-typedef void (*hash_func_t)(const void *p, struct siphash *state);
-typedef int (*compare_func_t)(const void *a, const void *b);
-
-struct hash_ops {
- hash_func_t hash;
- compare_func_t compare;
-};
-
-void string_hash_func(const void *p, struct siphash *state);
-int string_compare_func(const void *a, const void *b) _pure_;
-extern const struct hash_ops string_hash_ops;
-
-/* This will compare the passed pointers directly, and will not
- * dereference them. This is hence not useful for strings or
- * suchlike. */
-void trivial_hash_func(const void *p, struct siphash *state);
-int trivial_compare_func(const void *a, const void *b) _const_;
-extern const struct hash_ops trivial_hash_ops;
-
-/* 32bit values we can always just embedd in the pointer itself, but
- * in order to support 32bit archs we need store 64bit values
- * indirectly, since they don't fit in a pointer. */
-void uint64_hash_func(const void *p, struct siphash *state);
-int uint64_compare_func(const void *a, const void *b) _pure_;
-extern const struct hash_ops uint64_hash_ops;
-
-/* On some archs dev_t is 32bit, and on others 64bit. And sometimes
- * it's 64bit on 32bit archs, and sometimes 32bit on 64bit archs. Yuck! */
-#if SIZEOF_DEV_T != 8
-void devt_hash_func(const void *p, struct siphash *state) _pure_;
-int devt_compare_func(const void *a, const void *b) _pure_;
-extern const struct hash_ops devt_hash_ops = {
- .hash = devt_hash_func,
- .compare = devt_compare_func
-};
-#else
-#define devt_hash_func uint64_hash_func
-#define devt_compare_func uint64_compare_func
-#define devt_hash_ops uint64_hash_ops
-#endif
-
/* Macros for type checking */
#define PTR_COMPATIBLE_WITH_HASHMAP_BASE(h) \
(__builtin_types_compatible_p(typeof(h), HashmapBase*) || \
diff --git a/src/basic/log.c b/src/basic/log.c
index 1a9e6bdb91..a2bc0d5be2 100644
--- a/src/basic/log.c
+++ b/src/basic/log.c
@@ -352,7 +352,7 @@ static int write_to_console(
highlight = LOG_PRI(level) <= LOG_ERR && show_color;
if (show_location) {
- snprintf(location, sizeof(location), "(%s:%i) ", file, line);
+ xsprintf(location, "(%s:%i) ", file, line);
IOVEC_SET_STRING(iovec[n++], location);
}
@@ -777,7 +777,7 @@ static void log_assert(
return;
DISABLE_WARNING_FORMAT_NONLITERAL;
- snprintf(buffer, sizeof(buffer), format, text, file, line, func);
+ xsprintf(buffer, format, text, file, line, func);
REENABLE_WARNING;
log_abort_msg = buffer;
diff --git a/src/basic/macro.h b/src/basic/macro.h
index 5088e6720d..c529c6ecad 100644
--- a/src/basic/macro.h
+++ b/src/basic/macro.h
@@ -320,18 +320,47 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) {
#define SET_FLAG(v, flag, b) \
(v) = (b) ? ((v) | (flag)) : ((v) & ~(flag))
-#define IN_SET(x, y, ...) \
- ({ \
- static const typeof(y) _array[] = { (y), __VA_ARGS__ }; \
- const typeof(y) _x = (x); \
- unsigned _i; \
- bool _found = false; \
- for (_i = 0; _i < ELEMENTSOF(_array); _i++) \
- if (_array[_i] == _x) { \
- _found = true; \
- break; \
- } \
- _found; \
+#define CASE_F(X) case X:
+#define CASE_F_1(CASE, X) CASE_F(X)
+#define CASE_F_2(CASE, X, ...) CASE(X) CASE_F_1(CASE, __VA_ARGS__)
+#define CASE_F_3(CASE, X, ...) CASE(X) CASE_F_2(CASE, __VA_ARGS__)
+#define CASE_F_4(CASE, X, ...) CASE(X) CASE_F_3(CASE, __VA_ARGS__)
+#define CASE_F_5(CASE, X, ...) CASE(X) CASE_F_4(CASE, __VA_ARGS__)
+#define CASE_F_6(CASE, X, ...) CASE(X) CASE_F_5(CASE, __VA_ARGS__)
+#define CASE_F_7(CASE, X, ...) CASE(X) CASE_F_6(CASE, __VA_ARGS__)
+#define CASE_F_8(CASE, X, ...) CASE(X) CASE_F_7(CASE, __VA_ARGS__)
+#define CASE_F_9(CASE, X, ...) CASE(X) CASE_F_8(CASE, __VA_ARGS__)
+#define CASE_F_10(CASE, X, ...) CASE(X) CASE_F_9(CASE, __VA_ARGS__)
+#define CASE_F_11(CASE, X, ...) CASE(X) CASE_F_10(CASE, __VA_ARGS__)
+#define CASE_F_12(CASE, X, ...) CASE(X) CASE_F_11(CASE, __VA_ARGS__)
+#define CASE_F_13(CASE, X, ...) CASE(X) CASE_F_12(CASE, __VA_ARGS__)
+#define CASE_F_14(CASE, X, ...) CASE(X) CASE_F_13(CASE, __VA_ARGS__)
+#define CASE_F_15(CASE, X, ...) CASE(X) CASE_F_14(CASE, __VA_ARGS__)
+#define CASE_F_16(CASE, X, ...) CASE(X) CASE_F_15(CASE, __VA_ARGS__)
+#define CASE_F_17(CASE, X, ...) CASE(X) CASE_F_16(CASE, __VA_ARGS__)
+#define CASE_F_18(CASE, X, ...) CASE(X) CASE_F_17(CASE, __VA_ARGS__)
+#define CASE_F_19(CASE, X, ...) CASE(X) CASE_F_18(CASE, __VA_ARGS__)
+#define CASE_F_20(CASE, X, ...) CASE(X) CASE_F_19(CASE, __VA_ARGS__)
+
+#define GET_CASE_F(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,NAME,...) NAME
+#define FOR_EACH_MAKE_CASE(...) \
+ GET_CASE_F(__VA_ARGS__,CASE_F_20,CASE_F_19,CASE_F_18,CASE_F_17,CASE_F_16,CASE_F_15,CASE_F_14,CASE_F_13,CASE_F_12,CASE_F_11, \
+ CASE_F_10,CASE_F_9,CASE_F_8,CASE_F_7,CASE_F_6,CASE_F_5,CASE_F_4,CASE_F_3,CASE_F_2,CASE_F_1) \
+ (CASE_F,__VA_ARGS__)
+
+#define IN_SET(x, ...) \
+ ({ \
+ bool _found = false; \
+ /* If the build breaks in the line below, you need to extend the case macros */ \
+ static _unused_ char _static_assert__macros_need_to_be_extended[20 - sizeof((int[]){__VA_ARGS__})/sizeof(int)]; \
+ switch(x) { \
+ FOR_EACH_MAKE_CASE(__VA_ARGS__) \
+ _found = true; \
+ break; \
+ default: \
+ break; \
+ } \
+ _found; \
})
/* Define C11 thread_local attribute even on older gcc compiler
diff --git a/src/basic/missing.h b/src/basic/missing.h
index d539ed00e4..2d2785bead 100644
--- a/src/basic/missing.h
+++ b/src/basic/missing.h
@@ -131,6 +131,10 @@
#define NETLINK_LIST_MEMBERSHIPS 9
#endif
+#ifndef SOL_SCTP
+#define SOL_SCTP 132
+#endif
+
#if !HAVE_DECL_PIVOT_ROOT
static inline int pivot_root(const char *new_root, const char *put_old) {
return syscall(SYS_pivot_root, new_root, put_old);
@@ -1125,3 +1129,19 @@ static inline key_serial_t request_key(const char *type, const char *description
#ifndef KEY_SPEC_USER_KEYRING
#define KEY_SPEC_USER_KEYRING -4
#endif
+
+#ifndef PR_CAP_AMBIENT
+#define PR_CAP_AMBIENT 47
+#endif
+
+#ifndef PR_CAP_AMBIENT_IS_SET
+#define PR_CAP_AMBIENT_IS_SET 1
+#endif
+
+#ifndef PR_CAP_AMBIENT_RAISE
+#define PR_CAP_AMBIENT_RAISE 2
+#endif
+
+#ifndef PR_CAP_AMBIENT_CLEAR_ALL
+#define PR_CAP_AMBIENT_CLEAR_ALL 4
+#endif
diff --git a/src/basic/signal-util.c b/src/basic/signal-util.c
index 7637fccb2f..315efadd93 100644
--- a/src/basic/signal-util.c
+++ b/src/basic/signal-util.c
@@ -26,6 +26,7 @@
#include "macro.h"
#include "parse-util.h"
#include "signal-util.h"
+#include "stdio-util.h"
#include "string-table.h"
#include "string-util.h"
@@ -234,9 +235,9 @@ const char *signal_to_string(int signo) {
return name;
if (signo >= SIGRTMIN && signo <= SIGRTMAX)
- snprintf(buf, sizeof(buf), "RTMIN+%d", signo - SIGRTMIN);
+ xsprintf(buf, "RTMIN+%d", signo - SIGRTMIN);
else
- snprintf(buf, sizeof(buf), "%d", signo);
+ xsprintf(buf, "%d", signo);
return buf;
}
diff --git a/src/basic/siphash24.h b/src/basic/siphash24.h
index 3f7e20362b..54e2420cc6 100644
--- a/src/basic/siphash24.h
+++ b/src/basic/siphash24.h
@@ -16,6 +16,8 @@ struct siphash {
void siphash24_init(struct siphash *state, const uint8_t k[16]);
void siphash24_compress(const void *in, size_t inlen, struct siphash *state);
+#define siphash24_compress_byte(byte, state) siphash24_compress((const uint8_t[]) { (byte) }, 1, (state))
+
uint64_t siphash24_finalize(struct siphash *state);
uint64_t siphash24(const void *in, size_t inlen, const uint8_t k[16]);
diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c
index 79901a6a06..be144e157d 100644
--- a/src/basic/socket-util.c
+++ b/src/basic/socket-util.c
@@ -440,17 +440,10 @@ const char* socket_address_get_path(const SocketAddress *a) {
}
bool socket_ipv6_is_supported(void) {
- _cleanup_free_ char *l = NULL;
-
- if (access("/sys/module/ipv6", F_OK) != 0)
+ if (access("/proc/net/sockstat6", F_OK) != 0)
return false;
- /* If we can't check "disable" parameter, assume enabled */
- if (read_one_line_file("/sys/module/ipv6/parameters/disable", &l) < 0)
- return true;
-
- /* If module was loaded with disable=1 no IPv6 available */
- return l[0] == '0';
+ return true;
}
bool socket_address_matches_fd(const SocketAddress *a, int fd) {
diff --git a/src/basic/string-table.h b/src/basic/string-table.h
index 2181a3a767..588404ab5a 100644
--- a/src/basic/string-table.h
+++ b/src/basic/string-table.h
@@ -47,16 +47,34 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k
return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \
}
+#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) \
+ scope type name##_from_string(const char *s) { \
+ int b; \
+ b = parse_boolean(s); \
+ if (b == 0) \
+ return (type) 0; \
+ else if (b > 0) \
+ return yes; \
+ return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \
+ }
+
#define _DEFINE_STRING_TABLE_LOOKUP(name,type,scope) \
_DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \
_DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,scope) \
struct __useless_struct_to_allow_trailing_semicolon__
+#define _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,scope) \
+ _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \
+ _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) \
+ struct __useless_struct_to_allow_trailing_semicolon__
+
#define DEFINE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,)
#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,static)
#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,static)
#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,static)
+#define DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes) _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,)
+
/* For string conversions where numbers are also acceptable */
#define DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(name,type,max) \
int name##_to_string_alloc(type i, char **str) { \
diff --git a/src/basic/string-util.c b/src/basic/string-util.c
index 8178c7093f..849e457439 100644
--- a/src/basic/string-util.c
+++ b/src/basic/string-util.c
@@ -317,14 +317,33 @@ char *truncate_nl(char *s) {
return s;
}
+char ascii_tolower(char x) {
+
+ if (x >= 'A' && x <= 'Z')
+ return x - 'A' + 'a';
+
+ return x;
+}
+
char *ascii_strlower(char *t) {
char *p;
assert(t);
for (p = t; *p; p++)
- if (*p >= 'A' && *p <= 'Z')
- *p = *p - 'A' + 'a';
+ *p = ascii_tolower(*p);
+
+ return t;
+}
+
+char *ascii_strlower_n(char *t, size_t n) {
+ size_t i;
+
+ if (n <= 0)
+ return t;
+
+ for (i = 0; i < n; i++)
+ t[i] = ascii_tolower(t[i]);
return t;
}
diff --git a/src/basic/string-util.h b/src/basic/string-util.h
index b59b9b5a71..1ac6bcd6f8 100644
--- a/src/basic/string-util.h
+++ b/src/basic/string-util.h
@@ -130,7 +130,9 @@ char *strstrip(char *s);
char *delete_chars(char *s, const char *bad);
char *truncate_nl(char *s);
-char *ascii_strlower(char *path);
+char ascii_tolower(char x);
+char *ascii_strlower(char *s);
+char *ascii_strlower_n(char *s, size_t n);
bool chars_intersect(const char *a, const char *b) _pure_;
diff --git a/src/bootchart/svg.c b/src/bootchart/svg.c
index 2bf473ffc1..79e261abe5 100644
--- a/src/bootchart/svg.c
+++ b/src/bootchart/svg.c
@@ -37,6 +37,7 @@
#include "fileio.h"
#include "list.h"
#include "macro.h"
+#include "stdio-util.h"
#include "store.h"
#include "svg.h"
#include "utf8.h"
@@ -171,7 +172,7 @@ static int svg_title(FILE *of, const char *build, int pscount, double log_start,
strncpy(rootbdev, &c[10], sizeof(rootbdev) - 1);
rootbdev[3] = '\0';
- snprintf(filename, sizeof(filename), "/sys/block/%s/device/model", rootbdev);
+ xsprintf(filename, "/sys/block/%s/device/model", rootbdev);
r = read_one_line_file(filename, &model);
if (r < 0)
diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c
index 0a5c11ad0c..4894296554 100644
--- a/src/cgtop/cgtop.c
+++ b/src/cgtop/cgtop.c
@@ -40,6 +40,7 @@
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
+#include "stdio-util.h"
#include "terminal-util.h"
#include "unit-name.h"
#include "util.h"
@@ -565,9 +566,9 @@ static void display(Hashmap *a) {
}
if (arg_cpu_type == CPU_PERCENT)
- snprintf(buffer, sizeof(buffer), "%6s", "%CPU");
+ xsprintf(buffer, "%6s", "%CPU");
else
- snprintf(buffer, sizeof(buffer), "%*s", maxtcpu, "CPU Time");
+ xsprintf(buffer, "%*s", maxtcpu, "CPU Time");
rows = lines();
if (rows <= 10)
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
index 093179c003..c2238c8c43 100644
--- a/src/core/dbus-execute.c
+++ b/src/core/dbus-execute.c
@@ -293,9 +293,25 @@ static int property_get_capability_bounding_set(
assert(reply);
assert(c);
- /* We store this negated internally, to match the kernel, but
- * we expose it normalized. */
- return sd_bus_message_append(reply, "t", ~c->capability_bounding_set_drop);
+ return sd_bus_message_append(reply, "t", c->capability_bounding_set);
+}
+
+static int property_get_ambient_capabilities(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ ExecContext *c = userdata;
+
+ assert(bus);
+ assert(reply);
+ assert(c);
+
+ return sd_bus_message_append(reply, "t", c->capability_ambient_set);
}
static int property_get_capabilities(
@@ -632,21 +648,37 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("PassEnvironment", "as", NULL, offsetof(ExecContext, pass_environment), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("UMask", "u", bus_property_get_mode, offsetof(ExecContext, umask), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LimitCPU", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("LimitCPUSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LimitFSIZE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("LimitFSIZESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LimitDATA", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_DATA]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("LimitDATASoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_DATA]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LimitSTACK", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_STACK]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("LimitSTACKSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_STACK]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LimitCORE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CORE]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("LimitCORESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CORE]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LimitRSS", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RSS]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("LimitRSSSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RSS]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LimitNOFILE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NOFILE]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("LimitNOFILESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NOFILE]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LimitAS", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_AS]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("LimitASSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_AS]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LimitNPROC", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NPROC]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("LimitNPROCSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NPROC]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LimitMEMLOCK", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_MEMLOCK]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("LimitMEMLOCKSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_MEMLOCK]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LimitLOCKS", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_LOCKS]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("LimitLOCKSSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_LOCKS]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LimitSIGPENDING", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_SIGPENDING]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("LimitSIGPENDINGSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_SIGPENDING]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LimitMSGQUEUE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_MSGQUEUE]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("LimitMSGQUEUESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_MSGQUEUE]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LimitNICE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NICE]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("LimitNICESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NICE]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LimitRTPRIO", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RTPRIO]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("LimitRTPRIOSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RTPRIO]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LimitRTTIME", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("LimitRTTIMESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("WorkingDirectory", "s", property_get_working_directory, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RootDirectory", "s", NULL, offsetof(ExecContext, root_directory), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OOMScoreAdjust", "i", property_get_oom_score_adjust, 0, SD_BUS_VTABLE_PROPERTY_CONST),
@@ -673,6 +705,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("Capabilities", "s", property_get_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("SecureBits", "i", bus_property_get_int, offsetof(ExecContext, secure_bits), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("CapabilityBoundingSet", "t", property_get_capability_bounding_set, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("AmbientCapabilities", "t", property_get_ambient_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("User", "s", NULL, offsetof(ExecContext, user), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Group", "s", NULL, offsetof(ExecContext, group), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST),
diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
index 5457b2451b..8a523cc8ac 100644
--- a/src/core/dbus-manager.c
+++ b/src/core/dbus-manager.c
@@ -229,7 +229,10 @@ static int property_set_log_level(
if (r < 0)
return r;
- return log_set_max_level_from_string(t);
+ r = log_set_max_level_from_string(t);
+ if (r == 0)
+ log_info("Setting log level to %s.", t);
+ return r;
}
static int property_get_n_names(
@@ -1939,21 +1942,37 @@ const sd_bus_vtable bus_manager_vtable[] = {
SD_BUS_PROPERTY("DefaultMemoryAccounting", "b", bus_property_get_bool, offsetof(Manager, default_memory_accounting), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultTasksAccounting", "b", bus_property_get_bool, offsetof(Manager, default_tasks_accounting), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultLimitCPU", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("DefaultLimitCPUSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultLimitFSIZE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("DefaultLimitFSIZESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultLimitDATA", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_DATA]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("DefaultLimitDATASoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_DATA]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultLimitSTACK", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_STACK]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("DefaultLimitSTACKSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_STACK]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultLimitCORE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_CORE]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("DefaultLimitCORESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_CORE]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultLimitRSS", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RSS]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("DefaultLimitRSSSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RSS]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultLimitNOFILE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NOFILE]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("DefaultLimitNOFILESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NOFILE]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultLimitAS", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_AS]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("DefaultLimitASSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_AS]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultLimitNPROC", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NPROC]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("DefaultLimitNPROCSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NPROC]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultLimitMEMLOCK", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_MEMLOCK]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("DefaultLimitMEMLOCKSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_MEMLOCK]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultLimitLOCKS", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_LOCKS]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("DefaultLimitLOCKSSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_LOCKS]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultLimitSIGPENDING", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_SIGPENDING]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("DefaultLimitSIGPENDINGSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_SIGPENDING]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultLimitMSGQUEUE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_MSGQUEUE]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("DefaultLimitMSGQUEUESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_MSGQUEUE]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultLimitNICE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NICE]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("DefaultLimitNICESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NICE]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultLimitRTPRIO", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTPRIO]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("DefaultLimitRTPRIOSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTPRIO]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultLimitRTTIME", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("DefaultLimitRTTIMESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DefaultTasksMax", "t", NULL, offsetof(Manager, default_tasks_max), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("TimerSlackNSec", "t", property_get_timer_slack_nsec, 0, SD_BUS_VTABLE_PROPERTY_CONST),
diff --git a/src/core/dbus.c b/src/core/dbus.c
index e7ee216f0e..1d89b9e250 100644
--- a/src/core/dbus.c
+++ b/src/core/dbus.c
@@ -734,9 +734,11 @@ static int bus_on_connection(sd_event_source *s, int fd, uint32_t revents, void
return 0;
}
-static int bus_list_names(Manager *m, sd_bus *bus) {
+int manager_sync_bus_names(Manager *m, sd_bus *bus) {
_cleanup_strv_free_ char **names = NULL;
- char **i;
+ const char *name;
+ Iterator i;
+ Unit *u;
int r;
assert(m);
@@ -746,15 +748,55 @@ static int bus_list_names(Manager *m, sd_bus *bus) {
if (r < 0)
return log_error_errno(r, "Failed to get initial list of names: %m");
- /* This is a bit hacky, we say the owner of the name is the
- * name itself, because we don't want the extra traffic to
- * figure out the real owner. */
- STRV_FOREACH(i, names) {
- Unit *u;
+ /* We have to synchronize the current bus names with the
+ * list of active services. To do this, walk the list of
+ * all units with bus names. */
+ HASHMAP_FOREACH_KEY(u, name, m->watch_bus, i) {
+ Service *s = SERVICE(u);
+
+ assert(s);
- u = hashmap_get(m->watch_bus, *i);
- if (u)
- UNIT_VTABLE(u)->bus_name_owner_change(u, *i, NULL, *i);
+ if (!streq_ptr(s->bus_name, name)) {
+ log_unit_warning(u, "Bus name has changed from %s → %s, ignoring.", s->bus_name, name);
+ continue;
+ }
+
+ /* Check if a service's bus name is in the list of currently
+ * active names */
+ if (strv_contains(names, name)) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ const char *unique;
+
+ /* If it is, determine its current owner */
+ r = sd_bus_get_name_creds(bus, name, SD_BUS_CREDS_UNIQUE_NAME, &creds);
+ if (r < 0) {
+ log_error_errno(r, "Failed to get bus name owner %s: %m", name);
+ continue;
+ }
+
+ r = sd_bus_creds_get_unique_name(creds, &unique);
+ if (r < 0) {
+ log_error_errno(r, "Failed to get unique name for %s: %m", name);
+ continue;
+ }
+
+ /* Now, let's compare that to the previous bus owner, and
+ * if it's still the same, all is fine, so just don't
+ * bother the service. Otherwise, the name has apparently
+ * changed, so synthesize a name owner changed signal. */
+
+ if (!streq_ptr(unique, s->bus_name_owner))
+ UNIT_VTABLE(u)->bus_name_owner_change(u, name, s->bus_name_owner, unique);
+ } else {
+ /* So, the name we're watching is not on the bus.
+ * This either means it simply hasn't appeared yet,
+ * or it was lost during the daemon reload.
+ * Check if the service has a stored name owner,
+ * and synthesize a name loss signal in this case. */
+
+ if (s->bus_name_owner)
+ UNIT_VTABLE(u)->bus_name_owner_change(u, name, s->bus_name_owner, NULL);
+ }
}
return 0;
@@ -808,7 +850,9 @@ static int bus_setup_api(Manager *m, sd_bus *bus) {
if (r < 0)
return log_error_errno(r, "Failed to register name: %m");
- bus_list_names(m, bus);
+ r = manager_sync_bus_names(m, bus);
+ if (r < 0)
+ return r;
log_debug("Successfully connected to API bus.");
return 0;
diff --git a/src/core/dbus.h b/src/core/dbus.h
index 4f06ad11c4..ff761668f3 100644
--- a/src/core/dbus.h
+++ b/src/core/dbus.h
@@ -34,6 +34,8 @@ void bus_track_serialize(sd_bus_track *t, FILE *f);
int bus_track_deserialize_item(char ***l, const char *line);
int bus_track_coldplug(Manager *m, sd_bus_track **t, char ***l);
+int manager_sync_bus_names(Manager *m, sd_bus *bus);
+
int bus_foreach_bus(Manager *m, sd_bus_track *subscribed2, int (*send_message)(sd_bus *bus, void *userdata), void *userdata);
int bus_verify_manage_units_async(Manager *m, sd_bus_message *call, sd_bus_error *error);
diff --git a/src/core/execute.c b/src/core/execute.c
index 4f67a9de83..ac91568b63 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -737,12 +737,7 @@ static int enforce_user(const ExecContext *context, uid_t uid) {
/* Sets (but doesn't lookup) the uid and make sure we keep the
* capabilities while doing so. */
- if (context->capabilities) {
- _cleanup_cap_free_ cap_t d = NULL;
- static const cap_value_t bits[] = {
- CAP_SETUID, /* Necessary so that we can run setresuid() below */
- CAP_SETPCAP /* Necessary so that we can set PR_SET_SECUREBITS later on */
- };
+ if (context->capabilities || context->capability_ambient_set != 0) {
/* First step: If we need to keep capabilities but
* drop privileges we need to make sure we keep our
@@ -758,16 +753,24 @@ static int enforce_user(const ExecContext *context, uid_t uid) {
/* Second step: set the capabilities. This will reduce
* the capabilities to the minimum we need. */
- d = cap_dup(context->capabilities);
- if (!d)
- return -errno;
+ if (context->capabilities) {
+ _cleanup_cap_free_ cap_t d = NULL;
+ static const cap_value_t bits[] = {
+ CAP_SETUID, /* Necessary so that we can run setresuid() below */
+ CAP_SETPCAP /* Necessary so that we can set PR_SET_SECUREBITS later on */
+ };
- if (cap_set_flag(d, CAP_EFFECTIVE, ELEMENTSOF(bits), bits, CAP_SET) < 0 ||
- cap_set_flag(d, CAP_PERMITTED, ELEMENTSOF(bits), bits, CAP_SET) < 0)
- return -errno;
+ d = cap_dup(context->capabilities);
+ if (!d)
+ return -errno;
- if (cap_set_proc(d) < 0)
- return -errno;
+ if (cap_set_flag(d, CAP_EFFECTIVE, ELEMENTSOF(bits), bits, CAP_SET) < 0 ||
+ cap_set_flag(d, CAP_PERMITTED, ELEMENTSOF(bits), bits, CAP_SET) < 0)
+ return -errno;
+
+ if (cap_set_proc(d) < 0)
+ return -errno;
+ }
}
/* Third step: actually set the uids */
@@ -1856,6 +1859,8 @@ static int exec_child(
if (params->apply_permissions) {
+ int secure_bits = context->secure_bits;
+
for (i = 0; i < _RLIMIT_MAX; i++) {
if (!context->rlimit[i])
continue;
@@ -1866,28 +1871,71 @@ static int exec_child(
}
}
- if (context->capability_bounding_set_drop) {
- r = capability_bounding_set_drop(context->capability_bounding_set_drop, false);
+ if (!cap_test_all(context->capability_bounding_set)) {
+ r = capability_bounding_set_drop(context->capability_bounding_set, false);
if (r < 0) {
*exit_status = EXIT_CAPABILITIES;
return r;
}
}
+ /* This is done before enforce_user, but ambient set
+ * does not survive over setresuid() if keep_caps is not set. */
+ if (context->capability_ambient_set != 0) {
+ r = capability_ambient_set_apply(context->capability_ambient_set, true);
+ if (r < 0) {
+ *exit_status = EXIT_CAPABILITIES;
+ return r;
+ }
+
+ if (context->capabilities) {
+
+ /* The capabilities in ambient set need to be also in the inherited
+ * set. If they aren't, trying to get them will fail. Add the ambient
+ * set inherited capabilities to the capability set in the context.
+ * This is needed because if capabilities are set (using "Capabilities="
+ * keyword), they will override whatever we set now. */
+
+ r = capability_update_inherited_set(context->capabilities, context->capability_ambient_set);
+ if (r < 0) {
+ *exit_status = EXIT_CAPABILITIES;
+ return r;
+ }
+ }
+ }
+
if (context->user) {
r = enforce_user(context, uid);
if (r < 0) {
*exit_status = EXIT_USER;
return r;
}
+ if (context->capability_ambient_set != 0) {
+
+ /* Fix the ambient capabilities after user change. */
+ r = capability_ambient_set_apply(context->capability_ambient_set, false);
+ if (r < 0) {
+ *exit_status = EXIT_CAPABILITIES;
+ return r;
+ }
+
+ /* If we were asked to change user and ambient capabilities
+ * were requested, we had to add keep-caps to the securebits
+ * so that we would maintain the inherited capability set
+ * through the setresuid(). Make sure that the bit is added
+ * also to the context secure_bits so that we don't try to
+ * drop the bit away next. */
+
+ secure_bits |= 1<<SECURE_KEEP_CAPS;
+ }
}
/* PR_GET_SECUREBITS is not privileged, while
* PR_SET_SECUREBITS is. So to suppress
* potential EPERMs we'll try not to call
* PR_SET_SECUREBITS unless necessary. */
- if (prctl(PR_GET_SECUREBITS) != context->secure_bits)
- if (prctl(PR_SET_SECUREBITS, context->secure_bits) < 0) {
+ if (prctl(PR_GET_SECUREBITS) != secure_bits)
+ if (prctl(PR_SET_SECUREBITS, secure_bits) < 0) {
*exit_status = EXIT_SECUREBITS;
return -errno;
}
@@ -2114,6 +2162,7 @@ void exec_context_init(ExecContext *c) {
c->timer_slack_nsec = NSEC_INFINITY;
c->personality = PERSONALITY_INVALID;
c->runtime_directory_mode = 0755;
+ c->capability_bounding_set = CAP_ALL;
}
void exec_context_done(ExecContext *c) {
@@ -2413,9 +2462,12 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
prefix, c->oom_score_adjust);
for (i = 0; i < RLIM_NLIMITS; i++)
- if (c->rlimit[i])
- fprintf(f, "%s%s: " RLIM_FMT " " RLIM_FMT "\n",
- prefix, rlimit_to_string(i), c->rlimit[i]->rlim_cur, c->rlimit[i]->rlim_max);
+ if (c->rlimit[i]) {
+ fprintf(f, "%s%s: " RLIM_FMT "\n",
+ prefix, rlimit_to_string(i), c->rlimit[i]->rlim_max);
+ fprintf(f, "%s%sSoft: " RLIM_FMT "\n",
+ prefix, rlimit_to_string(i), c->rlimit[i]->rlim_cur);
+ }
if (c->ioprio_set) {
_cleanup_free_ char *class_str = NULL;
@@ -2514,12 +2566,23 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
(c->secure_bits & 1<<SECURE_NOROOT) ? " noroot" : "",
(c->secure_bits & 1<<SECURE_NOROOT_LOCKED) ? "noroot-locked" : "");
- if (c->capability_bounding_set_drop) {
+ if (c->capability_bounding_set != CAP_ALL) {
unsigned long l;
fprintf(f, "%sCapabilityBoundingSet:", prefix);
for (l = 0; l <= cap_last_cap(); l++)
- if (!(c->capability_bounding_set_drop & ((uint64_t) 1ULL << (uint64_t) l)))
+ if (c->capability_bounding_set & (UINT64_C(1) << l))
+ fprintf(f, " %s", strna(capability_to_name(l)));
+
+ fputs("\n", f);
+ }
+
+ if (c->capability_ambient_set != 0) {
+ unsigned long l;
+ fprintf(f, "%sAmbientCapabilities:", prefix);
+
+ for (l = 0; l <= cap_last_cap(); l++)
+ if (c->capability_ambient_set & (UINT64_C(1) << l))
fprintf(f, " %s", strna(capability_to_name(l)));
fputs("\n", f);
diff --git a/src/core/execute.h b/src/core/execute.h
index be5be9f531..8649620830 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -155,7 +155,9 @@ struct ExecContext {
char **read_write_dirs, **read_only_dirs, **inaccessible_dirs;
unsigned long mount_flags;
- uint64_t capability_bounding_set_drop;
+ uint64_t capability_bounding_set;
+
+ uint64_t capability_ambient_set;
cap_t capabilities;
int secure_bits;
diff --git a/src/core/job.c b/src/core/job.c
index 9654590635..274c554da9 100644
--- a/src/core/job.c
+++ b/src/core/job.c
@@ -35,6 +35,7 @@
#include "parse-util.h"
#include "set.h"
#include "special.h"
+#include "stdio-util.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
@@ -754,7 +755,7 @@ static void job_log_status_message(Unit *u, JobType t, JobResult result) {
return;
DISABLE_WARNING_FORMAT_NONLITERAL;
- snprintf(buf, sizeof(buf), format, unit_description(u));
+ xsprintf(buf, format, unit_description(u));
REENABLE_WARNING;
switch (t) {
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
index 0408b9a829..29ab1b6b9e 100644
--- a/src/core/load-fragment-gperf.gperf.m4
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -47,7 +47,8 @@ $1.SyslogLevel, config_parse_log_level, 0,
$1.SyslogLevelPrefix, config_parse_bool, 0, offsetof($1, exec_context.syslog_level_prefix)
$1.Capabilities, config_parse_exec_capabilities, 0, offsetof($1, exec_context)
$1.SecureBits, config_parse_exec_secure_bits, 0, offsetof($1, exec_context)
-$1.CapabilityBoundingSet, config_parse_bounding_set, 0, offsetof($1, exec_context.capability_bounding_set_drop)
+$1.CapabilityBoundingSet, config_parse_capability_set, 0, offsetof($1, exec_context.capability_bounding_set)
+$1.AmbientCapabilities, config_parse_capability_set, 0, offsetof($1, exec_context.capability_ambient_set)
$1.TimerSlackNSec, config_parse_nsec, 0, offsetof($1, exec_context.timer_slack_nsec)
$1.NoNewPrivileges, config_parse_no_new_privileges, 0, offsetof($1, exec_context)
m4_ifdef(`HAVE_SECCOMP',
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index cb553e1252..d3880b4e3c 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -38,6 +38,7 @@
#include "bus-internal.h"
#include "bus-util.h"
#include "cap-list.h"
+#include "capability-util.h"
#include "cgroup.h"
#include "conf-parser.h"
#include "cpu-set-util.h"
@@ -1024,7 +1025,7 @@ int config_parse_exec_secure_bits(const char *unit,
return 0;
}
-int config_parse_bounding_set(
+int config_parse_capability_set(
const char *unit,
const char *filename,
unsigned line,
@@ -1036,8 +1037,8 @@ int config_parse_bounding_set(
void *data,
void *userdata) {
- uint64_t *capability_bounding_set_drop = data;
- uint64_t capability_bounding_set, sum = 0;
+ uint64_t *capability_set = data;
+ uint64_t sum = 0, initial = 0;
bool invert = false;
const char *p;
@@ -1051,10 +1052,9 @@ int config_parse_bounding_set(
rvalue++;
}
- /* Note that we store this inverted internally, since the
- * kernel wants it like this. But we actually expose it
- * non-inverted everywhere to have a fully normalized
- * interface. */
+ if (strcmp(lvalue, "CapabilityBoundingSet") == 0)
+ initial = CAP_ALL; /* initialized to all bits on */
+ /* else "AmbientCapabilities" initialized to all bits off */
p = rvalue;
for (;;) {
@@ -1073,18 +1073,21 @@ int config_parse_bounding_set(
cap = capability_from_name(word);
if (cap < 0) {
- log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse capability in bounding set, ignoring: %s", word);
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse capability in bounding/ambient set, ignoring: %s", word);
continue;
}
sum |= ((uint64_t) UINT64_C(1)) << (uint64_t) cap;
}
- capability_bounding_set = invert ? ~sum : sum;
- if (*capability_bounding_set_drop != 0 && capability_bounding_set != 0)
- *capability_bounding_set_drop = ~(~*capability_bounding_set_drop | capability_bounding_set);
+ sum = invert ? ~sum : sum;
+
+ if (sum == 0 || *capability_set == initial)
+ /* "" or uninitialized data -> replace */
+ *capability_set = sum;
else
- *capability_bounding_set_drop = ~capability_bounding_set;
+ /* previous data -> merge */
+ *capability_set |= sum;
return 0;
}
@@ -4002,7 +4005,7 @@ void unit_dump_config_items(FILE *f) {
{ config_parse_log_level, "LEVEL" },
{ config_parse_exec_capabilities, "CAPABILITIES" },
{ config_parse_exec_secure_bits, "SECUREBITS" },
- { config_parse_bounding_set, "BOUNDINGSET" },
+ { config_parse_capability_set, "BOUNDINGSET" },
{ config_parse_limit, "LIMIT" },
{ config_parse_unit_deps, "UNIT [...]" },
{ config_parse_exec, "PATH [ARGUMENT [...]]" },
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index a451fc164a..f0027a6b43 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -56,7 +56,7 @@ int config_parse_exec_cpu_sched_prio(const char *unit, const char *filename, uns
int config_parse_exec_cpu_affinity(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_exec_capabilities(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_exec_secure_bits(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_bounding_set(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_capability_set(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_limit(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_bytes_limit(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_sec_limit(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/core/main.c b/src/core/main.c
index f9de54028e..7a428fcccf 100644
--- a/src/core/main.c
+++ b/src/core/main.c
@@ -117,7 +117,7 @@ static usec_t arg_runtime_watchdog = 0;
static usec_t arg_shutdown_watchdog = 10 * USEC_PER_MINUTE;
static char **arg_default_environment = NULL;
static struct rlimit *arg_default_rlimit[_RLIMIT_MAX] = {};
-static uint64_t arg_capability_bounding_set_drop = 0;
+static uint64_t arg_capability_bounding_set = CAP_ALL;
static nsec_t arg_timer_slack_nsec = NSEC_INFINITY;
static usec_t arg_default_timer_accuracy_usec = 1 * USEC_PER_MINUTE;
static Set* arg_syscall_archs = NULL;
@@ -644,7 +644,7 @@ static int parse_config_file(void) {
{ "Manager", "JoinControllers", config_parse_join_controllers, 0, &arg_join_controllers },
{ "Manager", "RuntimeWatchdogSec", config_parse_sec, 0, &arg_runtime_watchdog },
{ "Manager", "ShutdownWatchdogSec", config_parse_sec, 0, &arg_shutdown_watchdog },
- { "Manager", "CapabilityBoundingSet", config_parse_bounding_set, 0, &arg_capability_bounding_set_drop },
+ { "Manager", "CapabilityBoundingSet", config_parse_capability_set, 0, &arg_capability_bounding_set },
#ifdef HAVE_SECCOMP
{ "Manager", "SystemCallArchitectures", config_parse_syscall_archs, 0, &arg_syscall_archs },
#endif
@@ -1631,14 +1631,14 @@ int main(int argc, char *argv[]) {
if (prctl(PR_SET_TIMERSLACK, arg_timer_slack_nsec) < 0)
log_error_errno(errno, "Failed to adjust timer slack: %m");
- if (arg_capability_bounding_set_drop) {
- r = capability_bounding_set_drop_usermode(arg_capability_bounding_set_drop);
+ if (!cap_test_all(arg_capability_bounding_set)) {
+ r = capability_bounding_set_drop_usermode(arg_capability_bounding_set);
if (r < 0) {
log_emergency_errno(r, "Failed to drop capability bounding set of usermode helpers: %m");
error_message = "Failed to drop capability bounding set of usermode helpers";
goto finish;
}
- r = capability_bounding_set_drop(arg_capability_bounding_set_drop, true);
+ r = capability_bounding_set_drop(arg_capability_bounding_set, true);
if (r < 0) {
log_emergency_errno(r, "Failed to drop capability bounding set: %m");
error_message = "Failed to drop capability bounding set";
diff --git a/src/core/manager.c b/src/core/manager.c
index 34dd715e93..711b0cdcee 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -380,6 +380,9 @@ static int enable_special_signals(Manager *m) {
assert(m);
+ if (m->test_run)
+ return 0;
+
/* Enable that we get SIGINT on control-alt-del. In containers
* this will fail with EPERM (older) or EINVAL (newer), so
* ignore that. */
@@ -1885,23 +1888,21 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t
switch (sfsi.ssi_signo - SIGRTMIN) {
case 20:
- log_debug("Enabling showing of status.");
manager_set_show_status(m, SHOW_STATUS_YES);
break;
case 21:
- log_debug("Disabling showing of status.");
manager_set_show_status(m, SHOW_STATUS_NO);
break;
case 22:
log_set_max_level(LOG_DEBUG);
- log_notice("Setting log level to debug.");
+ log_info("Setting log level to debug.");
break;
case 23:
log_set_max_level(LOG_INFO);
- log_notice("Setting log level to info.");
+ log_info("Setting log level to info.");
break;
case 24:
@@ -2576,6 +2577,10 @@ int manager_reload(Manager *m) {
/* Third, fire things up! */
manager_coldplug(m);
+ /* Sync current state of bus names with our set of listening units */
+ if (m->api_bus)
+ manager_sync_bus_names(m, m->api_bus);
+
assert(m->n_reloading > 0);
m->n_reloading--;
@@ -2961,6 +2966,9 @@ void manager_set_show_status(Manager *m, ShowStatus mode) {
if (m->running_as != MANAGER_SYSTEM)
return;
+ if (m->show_status != mode)
+ log_debug("%s showing of status.",
+ mode == SHOW_STATUS_NO ? "Disabling" : "Enabling");
m->show_status = mode;
if (mode > 0)
diff --git a/src/core/mount-setup.c b/src/core/mount-setup.c
index 2b8d590ed1..d73b319c5d 100644
--- a/src/core/mount-setup.c
+++ b/src/core/mount-setup.c
@@ -304,13 +304,18 @@ int mount_cgroup_controllers(char ***join_controllers) {
return log_oom();
r = symlink(options, t);
- if (r < 0 && errno != EEXIST)
- return log_error_errno(errno, "Failed to create symlink %s: %m", t);
+ if (r >= 0) {
#ifdef SMACK_RUN_LABEL
- r = mac_smack_copy(t, options);
- if (r < 0 && r != -EOPNOTSUPP)
- return log_error_errno(r, "Failed to copy smack label from %s to %s: %m", options, t);
+ _cleanup_free_ char *src;
+ src = strappend("/sys/fs/cgroup/", options);
+ if (!src)
+ return log_oom();
+ r = mac_smack_copy(t, src);
+ if (r < 0 && r != -EOPNOTSUPP)
+ return log_error_errno(r, "Failed to copy smack label from %s to %s: %m", src, t);
#endif
+ } else if (errno != EEXIST)
+ return log_error_errno(errno, "Failed to create symlink %s: %m", t);
}
}
}
diff --git a/src/core/service.c b/src/core/service.c
index 41a729c421..c5b689a35c 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -323,6 +323,8 @@ static void service_done(Unit *u) {
s->bus_name = mfree(s->bus_name);
}
+ s->bus_name_owner = mfree(s->bus_name_owner);
+
s->bus_endpoint_fd = safe_close(s->bus_endpoint_fd);
service_close_socket_fd(s);
service_connection_unref(s);
@@ -2122,6 +2124,7 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) {
unit_serialize_item(u, f, "main-pid-known", yes_no(s->main_pid_known));
unit_serialize_item(u, f, "bus-name-good", yes_no(s->bus_name_good));
+ unit_serialize_item(u, f, "bus-name-owner", s->bus_name_owner);
r = unit_serialize_item_escaped(u, f, "status-text", s->status_text);
if (r < 0)
@@ -2249,6 +2252,10 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value,
log_unit_debug(u, "Failed to parse bus-name-good value: %s", value);
else
s->bus_name_good = b;
+ } else if (streq(key, "bus-name-owner")) {
+ r = free_and_strdup(&s->bus_name_owner, value);
+ if (r < 0)
+ log_unit_error_errno(u, r, "Unable to deserialize current bus owner %s: %m", value);
} else if (streq(key, "status-text")) {
char *t;
@@ -3134,6 +3141,13 @@ static void service_bus_name_owner_change(
s->bus_name_good = !!new_owner;
+ /* Track the current owner, so we can reconstruct changes after a daemon reload */
+ r = free_and_strdup(&s->bus_name_owner, new_owner);
+ if (r < 0) {
+ log_unit_error_errno(u, r, "Unable to set new bus name owner %s: %m", new_owner);
+ return;
+ }
+
if (s->type == SERVICE_DBUS) {
/* service_enter_running() will figure out what to
diff --git a/src/core/service.h b/src/core/service.h
index d0faad88e0..19efbccfc7 100644
--- a/src/core/service.h
+++ b/src/core/service.h
@@ -172,6 +172,7 @@ struct Service {
bool reset_cpu_usage:1;
char *bus_name;
+ char *bus_name_owner; /* unique name of the current owner */
char *status_text;
int status_errno;
diff --git a/src/core/smack-setup.c b/src/core/smack-setup.c
index 0661ff9ecd..c9374ca0e8 100644
--- a/src/core/smack-setup.c
+++ b/src/core/smack-setup.c
@@ -197,6 +197,75 @@ static int write_cipso2_rules(const char* srcdir) {
return r;
}
+static int write_netlabel_rules(const char* srcdir) {
+ _cleanup_fclose_ FILE *dst = NULL;
+ _cleanup_closedir_ DIR *dir = NULL;
+ struct dirent *entry;
+ char buf[NAME_MAX];
+ int dfd = -1;
+ int r = 0;
+
+ dst = fopen("/sys/fs/smackfs/netlabel", "we");
+ if (!dst) {
+ if (errno != ENOENT)
+ log_warning_errno(errno, "Failed to open /sys/fs/smackfs/netlabel: %m");
+ return -errno; /* negative error */
+ }
+
+ /* write rules to dst from every file in the directory */
+ dir = opendir(srcdir);
+ if (!dir) {
+ if (errno != ENOENT)
+ log_warning_errno(errno, "Failed to opendir %s: %m", srcdir);
+ return errno; /* positive on purpose */
+ }
+
+ dfd = dirfd(dir);
+ assert(dfd >= 0);
+
+ FOREACH_DIRENT(entry, dir, return 0) {
+ int fd;
+ _cleanup_fclose_ FILE *policy = NULL;
+
+ fd = openat(dfd, entry->d_name, O_RDONLY|O_CLOEXEC);
+ if (fd < 0) {
+ if (r == 0)
+ r = -errno;
+ log_warning_errno(errno, "Failed to open %s: %m", entry->d_name);
+ continue;
+ }
+
+ policy = fdopen(fd, "re");
+ if (!policy) {
+ if (r == 0)
+ r = -errno;
+ safe_close(fd);
+ log_error_errno(errno, "Failed to open %s: %m", entry->d_name);
+ continue;
+ }
+
+ /* load2 write rules in the kernel require a line buffered stream */
+ FOREACH_LINE(buf, policy,
+ log_error_errno(errno, "Failed to read line from %s: %m",
+ entry->d_name)) {
+ if (!fputs(buf, dst)) {
+ if (r == 0)
+ r = -EINVAL;
+ log_error_errno(errno, "Failed to write line to /sys/fs/smackfs/netlabel");
+ break;
+ }
+ if (fflush(dst)) {
+ if (r == 0)
+ r = -errno;
+ log_error_errno(errno, "Failed to flush writes to /sys/fs/smackfs/netlabel: %m");
+ break;
+ }
+ }
+ }
+
+ return r;
+}
+
#endif
int mac_smack_setup(bool *loaded_policy) {
@@ -225,8 +294,18 @@ int mac_smack_setup(bool *loaded_policy) {
#ifdef SMACK_RUN_LABEL
r = write_string_file("/proc/self/attr/current", SMACK_RUN_LABEL, 0);
- if (r)
- log_warning_errno(r, "Failed to set SMACK label \"%s\" on self: %m", SMACK_RUN_LABEL);
+ if (r < 0)
+ log_warning_errno(r, "Failed to set SMACK label \"" SMACK_RUN_LABEL "\" on self: %m");
+ r = write_string_file("/sys/fs/smackfs/ambient", SMACK_RUN_LABEL, 0);
+ if (r < 0)
+ log_warning_errno(r, "Failed to set SMACK ambient label \"" SMACK_RUN_LABEL "\": %m");
+ r = write_string_file("/sys/fs/smackfs/netlabel",
+ "0.0.0.0/0 " SMACK_RUN_LABEL, 0);
+ if (r < 0)
+ log_warning_errno(r, "Failed to set SMACK netlabel rule \"0.0.0.0/0 " SMACK_RUN_LABEL "\": %m");
+ r = write_string_file("/sys/fs/smackfs/netlabel", "127.0.0.1 -CIPSO", 0);
+ if (r < 0)
+ log_warning_errno(r, "Failed to set SMACK netlabel rule \"127.0.0.1 -CIPSO\": %m");
#endif
r = write_cipso2_rules("/etc/smack/cipso.d/");
@@ -236,13 +315,29 @@ int mac_smack_setup(bool *loaded_policy) {
return 0;
case ENOENT:
log_debug("Smack/CIPSO access rules directory '/etc/smack/cipso.d/' not found");
- return 0;
+ break;
case 0:
log_info("Successfully loaded Smack/CIPSO policies.");
break;
default:
log_warning_errno(r, "Failed to load Smack/CIPSO access rules, ignoring: %m");
+ break;
+ }
+
+ r = write_netlabel_rules("/etc/smack/netlabel.d/");
+ switch(r) {
+ case -ENOENT:
+ log_debug("Smack/CIPSO is not enabled in the kernel.");
return 0;
+ case ENOENT:
+ log_debug("Smack network host rules directory '/etc/smack/netlabel.d/' not found");
+ break;
+ case 0:
+ log_info("Successfully loaded Smack network host rules.");
+ break;
+ default:
+ log_warning_errno(r, "Failed to load Smack network host rules: %m, ignoring.");
+ break;
}
*loaded_policy = true;
diff --git a/src/core/socket.c b/src/core/socket.c
index 7beec3644e..2e4173aabc 100644
--- a/src/core/socket.c
+++ b/src/core/socket.c
@@ -28,9 +28,9 @@
#include <sys/epoll.h>
#include <sys/stat.h>
#include <unistd.h>
+#include <linux/sctp.h>
#include "sd-event.h"
-
#include "alloc-util.h"
#include "bus-error.h"
#include "bus-util.h"
@@ -156,14 +156,16 @@ static void socket_done(Unit *u) {
s->tcp_congestion = mfree(s->tcp_congestion);
s->bind_to_device = mfree(s->bind_to_device);
- free(s->smack);
- free(s->smack_ip_in);
- free(s->smack_ip_out);
+ s->smack = mfree(s->smack);
+ s->smack_ip_in = mfree(s->smack_ip_in);
+ s->smack_ip_out = mfree(s->smack_ip_out);
strv_free(s->symlinks);
- free(s->user);
- free(s->group);
+ s->user = mfree(s->user);
+ s->group = mfree(s->group);
+
+ s->fdname = mfree(s->fdname);
s->timer_event_source = sd_event_source_unref(s->timer_event_source);
}
@@ -875,8 +877,14 @@ static void socket_apply_socket_options(Socket *s, int fd) {
if (s->no_delay) {
int b = s->no_delay;
- if (setsockopt(fd, SOL_TCP, TCP_NODELAY, &b, sizeof(b)) < 0)
- log_unit_warning_errno(UNIT(s), errno, "TCP_NODELAY failed: %m");
+
+ if (s->socket_protocol == IPPROTO_SCTP) {
+ if (setsockopt(fd, SOL_SCTP, SCTP_NODELAY, &b, sizeof(b)) < 0)
+ log_unit_warning_errno(UNIT(s), errno, "SCTP_NODELAY failed: %m");
+ } else {
+ if (setsockopt(fd, SOL_TCP, TCP_NODELAY, &b, sizeof(b)) < 0)
+ log_unit_warning_errno(UNIT(s), errno, "TCP_NODELAY failed: %m");
+ }
}
if (s->broadcast) {
diff --git a/src/core/transaction.c b/src/core/transaction.c
index 15e79d00b3..2f163190e9 100644
--- a/src/core/transaction.c
+++ b/src/core/transaction.c
@@ -950,7 +950,7 @@ int transaction_add_job_and_dependencies(
r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, false, false, false, ignore_order, e);
if (r < 0) {
log_unit_full(dep,
- r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_WARNING, r,
+ r == -EBADR /* unit masked */ ? LOG_DEBUG : LOG_WARNING, r,
"Cannot add dependency job, ignoring: %s",
bus_error_message(e, r));
sd_bus_error_free(e);
diff --git a/src/core/unit.c b/src/core/unit.c
index f935b6a601..32267d95f5 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -51,6 +51,7 @@
#include "set.h"
#include "special.h"
#include "stat-util.h"
+#include "stdio-util.h"
#include "string-util.h"
#include "strv.h"
#include "unit-name.h"
@@ -1412,7 +1413,7 @@ static void unit_status_log_starting_stopping_reloading(Unit *u, JobType t) {
format = unit_get_status_message_format(u, t);
DISABLE_WARNING_FORMAT_NONLITERAL;
- snprintf(buf, sizeof(buf), format, unit_description(u));
+ xsprintf(buf, format, unit_description(u));
REENABLE_WARNING;
mid = t == JOB_START ? SD_MESSAGE_UNIT_STARTING :
@@ -3119,7 +3120,7 @@ int unit_kill_common(
killed = true;
}
- if (r == 0 && !killed && IN_SET(who, KILL_ALL_FAIL, KILL_CONTROL_FAIL, KILL_ALL_FAIL))
+ if (r == 0 && !killed && IN_SET(who, KILL_ALL_FAIL, KILL_CONTROL_FAIL))
return -ESRCH;
return r;
@@ -3231,7 +3232,7 @@ int unit_patch_contexts(Unit *u) {
ec->no_new_privileges = true;
if (ec->private_devices)
- ec->capability_bounding_set_drop |= (uint64_t) 1ULL << (uint64_t) CAP_MKNOD;
+ ec->capability_bounding_set &= ~(UINT64_C(1) << CAP_MKNOD);
}
cc = unit_get_cgroup_context(u);
diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c
index d383041d39..84605fa267 100644
--- a/src/hostname/hostnamed.c
+++ b/src/hostname/hostnamed.c
@@ -212,7 +212,7 @@ try_dmi:
unreliable enough, so let's not do any additional guesswork
on top of that.
- See the SMBIOS Specification 4.0 section 7.4.1 for
+ See the SMBIOS Specification 3.0 section 7.4.1 for
details about the values listed here:
https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.0.0.pdf
diff --git a/src/import/import-common.c b/src/import/import-common.c
index a8551ca9e8..8a48bd7bf9 100644
--- a/src/import/import-common.c
+++ b/src/import/import-common.c
@@ -134,7 +134,7 @@ int import_fork_tar_x(const char *path, pid_t *ret) {
if (unshare(CLONE_NEWNET) < 0)
log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m");
- r = capability_bounding_set_drop(~retain, true);
+ r = capability_bounding_set_drop(retain, true);
if (r < 0)
log_error_errno(r, "Failed to drop capabilities, ignoring: %m");
@@ -208,7 +208,7 @@ int import_fork_tar_c(const char *path, pid_t *ret) {
if (unshare(CLONE_NEWNET) < 0)
log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m");
- r = capability_bounding_set_drop(~retain, true);
+ r = capability_bounding_set_drop(retain, true);
if (r < 0)
log_error_errno(r, "Failed to drop capabilities, ignoring: %m");
diff --git a/src/import/importd.c b/src/import/importd.c
index 1f308b36b3..1b777c32b6 100644
--- a/src/import/importd.c
+++ b/src/import/importd.c
@@ -55,7 +55,6 @@ typedef enum TransferType {
TRANSFER_EXPORT_RAW,
TRANSFER_PULL_TAR,
TRANSFER_PULL_RAW,
- TRANSFER_PULL_DKR,
_TRANSFER_TYPE_MAX,
_TRANSFER_TYPE_INVALID = -1,
} TransferType;
@@ -74,7 +73,6 @@ struct Transfer {
bool force_local;
bool read_only;
- char *dkr_index_url;
char *format;
pid_t pid;
@@ -117,7 +115,6 @@ static const char* const transfer_type_table[_TRANSFER_TYPE_MAX] = {
[TRANSFER_EXPORT_RAW] = "export-raw",
[TRANSFER_PULL_TAR] = "pull-tar",
[TRANSFER_PULL_RAW] = "pull-raw",
- [TRANSFER_PULL_DKR] = "pull-dkr",
};
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(transfer_type, TransferType);
@@ -134,7 +131,6 @@ static Transfer *transfer_unref(Transfer *t) {
free(t->remote);
free(t->local);
- free(t->dkr_index_url);
free(t->format);
free(t->object_path);
@@ -383,12 +379,11 @@ static int transfer_start(Transfer *t) {
if (t->pid == 0) {
const char *cmd[] = {
NULL, /* systemd-import, systemd-export or systemd-pull */
- NULL, /* tar, raw, dkr */
+ NULL, /* tar, raw */
NULL, /* --verify= */
NULL, /* verify argument */
NULL, /* maybe --force */
NULL, /* maybe --read-only */
- NULL, /* maybe --dkr-index-url */
NULL, /* if so: the actual URL */
NULL, /* maybe --format= */
NULL, /* if so: the actual format */
@@ -471,10 +466,8 @@ static int transfer_start(Transfer *t) {
if (IN_SET(t->type, TRANSFER_IMPORT_TAR, TRANSFER_EXPORT_TAR, TRANSFER_PULL_TAR))
cmd[k++] = "tar";
- else if (IN_SET(t->type, TRANSFER_IMPORT_RAW, TRANSFER_EXPORT_RAW, TRANSFER_PULL_RAW))
- cmd[k++] = "raw";
else
- cmd[k++] = "dkr";
+ cmd[k++] = "raw";
if (t->verify != _IMPORT_VERIFY_INVALID) {
cmd[k++] = "--verify";
@@ -486,11 +479,6 @@ static int transfer_start(Transfer *t) {
if (t->read_only)
cmd[k++] = "--read-only";
- if (t->dkr_index_url) {
- cmd[k++] = "--dkr-index-url";
- cmd[k++] = t->dkr_index_url;
- }
-
if (t->format) {
cmd[k++] = "--format";
cmd[k++] = t->format;
@@ -707,7 +695,7 @@ static int manager_new(Manager **ret) {
return 0;
}
-static Transfer *manager_find(Manager *m, TransferType type, const char *dkr_index_url, const char *remote) {
+static Transfer *manager_find(Manager *m, TransferType type, const char *remote) {
Transfer *t;
Iterator i;
@@ -718,8 +706,7 @@ static Transfer *manager_find(Manager *m, TransferType type, const char *dkr_ind
HASHMAP_FOREACH(t, m->transfers, i) {
if (t->type == type &&
- streq_ptr(t->remote, remote) &&
- streq_ptr(t->dkr_index_url, dkr_index_url))
+ streq_ptr(t->remote, remote))
return t;
}
@@ -907,7 +894,7 @@ static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_er
type = streq_ptr(sd_bus_message_get_member(msg), "PullTar") ? TRANSFER_PULL_TAR : TRANSFER_PULL_RAW;
- if (manager_find(m, type, NULL, remote))
+ if (manager_find(m, type, remote))
return sd_bus_error_setf(error, BUS_ERROR_TRANSFER_IN_PROGRESS, "Transfer for %s already in progress.", remote);
r = transfer_new(m, &t);
@@ -939,105 +926,6 @@ static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_er
return sd_bus_reply_method_return(msg, "uo", id, object);
}
-static int method_pull_dkr(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
- _cleanup_(transfer_unrefp) Transfer *t = NULL;
- const char *index_url, *remote, *tag, *local, *verify, *object;
- Manager *m = userdata;
- ImportVerify v;
- int force, r;
- uint32_t id;
-
- assert(msg);
- assert(m);
-
- r = bus_verify_polkit_async(
- msg,
- CAP_SYS_ADMIN,
- "org.freedesktop.import1.pull",
- NULL,
- false,
- UID_INVALID,
- &m->polkit_registry,
- error);
- if (r < 0)
- return r;
- if (r == 0)
- return 1; /* Will call us back */
-
- r = sd_bus_message_read(msg, "sssssb", &index_url, &remote, &tag, &local, &verify, &force);
- if (r < 0)
- return r;
-
- if (isempty(index_url))
- index_url = DEFAULT_DKR_INDEX_URL;
- if (!index_url)
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Index URL must be specified.");
- if (!http_url_is_valid(index_url))
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Index URL %s is invalid", index_url);
-
- if (!dkr_name_is_valid(remote))
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Remote name %s is not valid", remote);
-
- if (isempty(tag))
- tag = "latest";
- else if (!dkr_tag_is_valid(tag))
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Tag %s is not valid", tag);
-
- if (isempty(local))
- local = NULL;
- else if (!machine_name_is_valid(local))
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Local name %s is invalid", local);
-
- if (isempty(verify))
- v = IMPORT_VERIFY_SIGNATURE;
- else
- v = import_verify_from_string(verify);
- if (v < 0)
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown verification mode %s", verify);
-
- if (v != IMPORT_VERIFY_NO)
- return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "DKR does not support verification.");
-
- r = setup_machine_directory((uint64_t) -1, error);
- if (r < 0)
- return r;
-
- if (manager_find(m, TRANSFER_PULL_DKR, index_url, remote))
- return sd_bus_error_setf(error, BUS_ERROR_TRANSFER_IN_PROGRESS, "Transfer for %s already in progress.", remote);
-
- r = transfer_new(m, &t);
- if (r < 0)
- return r;
-
- t->type = TRANSFER_PULL_DKR;
- t->verify = v;
- t->force_local = force;
-
- t->dkr_index_url = strdup(index_url);
- if (!t->dkr_index_url)
- return -ENOMEM;
-
- t->remote = strjoin(remote, ":", tag, NULL);
- if (!t->remote)
- return -ENOMEM;
-
- if (local) {
- t->local = strdup(local);
- if (!t->local)
- return -ENOMEM;
- }
-
- r = transfer_start(t);
- if (r < 0)
- return r;
-
- object = t->object_path;
- id = t->id;
- t = NULL;
-
- return sd_bus_reply_method_return(msg, "uo", id, object);
-}
-
static int method_list_transfers(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
Manager *m = userdata;
@@ -1188,7 +1076,6 @@ static const sd_bus_vtable manager_vtable[] = {
SD_BUS_METHOD("ExportRaw", "shs", "uo", method_export_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("PullTar", "sssb", "uo", method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("PullRaw", "sssb", "uo", method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED),
- SD_BUS_METHOD("PullDkr", "sssssb", "uo", method_pull_dkr, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ListTransfers", NULL, "a(usssdo)", method_list_transfers, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("CancelTransfer", "u", NULL, method_cancel_transfer, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_SIGNAL("TransferNew", "uo", 0),
diff --git a/src/import/org.freedesktop.import1.conf b/src/import/org.freedesktop.import1.conf
index ae36af422f..ed2539a03b 100644
--- a/src/import/org.freedesktop.import1.conf
+++ b/src/import/org.freedesktop.import1.conf
@@ -53,10 +53,6 @@
send_member="PullRaw"/>
<allow send_destination="org.freedesktop.import1"
- send_interface="org.freedesktop.import1.Manager"
- send_member="PullDkr"/>
-
- <allow send_destination="org.freedesktop.import1"
send_interface="org.freedesktop.import1.Transfer"
send_member="Cancel"/>
diff --git a/src/import/pull-dkr.c b/src/import/pull-dkr.c
deleted file mode 100644
index 831470ff13..0000000000
--- a/src/import/pull-dkr.c
+++ /dev/null
@@ -1,1346 +0,0 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
-/***
- This file is part of systemd.
-
- Copyright 2014 Lennart Poettering
-
- systemd is free software; you can redistribute it and/or modify it
- under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation; either version 2.1 of the License, or
- (at your option) any later version.
-
- systemd is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License
- along with systemd; If not, see <http://www.gnu.org/licenses/>.
-***/
-
-#include <curl/curl.h>
-#include <sys/prctl.h>
-
-#include "sd-daemon.h"
-
-#include "alloc-util.h"
-#include "aufs-util.h"
-#include "btrfs-util.h"
-#include "curl-util.h"
-#include "fd-util.h"
-#include "fileio.h"
-#include "fs-util.h"
-#include "hostname-util.h"
-#include "import-common.h"
-#include "import-util.h"
-#include "json.h"
-#include "mkdir.h"
-#include "path-util.h"
-#include "process-util.h"
-#include "pull-common.h"
-#include "pull-dkr.h"
-#include "pull-job.h"
-#include "rm-rf.h"
-#include "string-util.h"
-#include "strv.h"
-#include "utf8.h"
-#include "web-util.h"
-
-typedef enum DkrProgress {
- DKR_SEARCHING,
- DKR_RESOLVING,
- DKR_METADATA,
- DKR_DOWNLOADING,
- DKR_COPYING,
-} DkrProgress;
-
-struct DkrPull {
- sd_event *event;
- CurlGlue *glue;
-
- char *index_protocol;
- char *index_address;
-
- char *index_url;
- char *image_root;
-
- PullJob *images_job;
- PullJob *tags_job;
- PullJob *ancestry_job;
- PullJob *json_job;
- PullJob *layer_job;
-
- char *name;
- char *reference;
- char *id;
-
- char *response_digest;
- char *response_token;
- char **response_registries;
-
- char **ancestry;
- unsigned n_ancestry;
- unsigned current_ancestry;
-
- DkrPullFinished on_finished;
- void *userdata;
-
- char *local;
- bool force_local;
- bool grow_machine_directory;
-
- char *temp_path;
- char *final_path;
-
- pid_t tar_pid;
-};
-
-#define PROTOCOL_PREFIX "https://"
-
-#define HEADER_TOKEN "X-Do" /* the HTTP header for the auth token */ "cker-Token:"
-#define HEADER_REGISTRY "X-Do" /* the HTTP header for the registry */ "cker-Endpoints:"
-#define HEADER_DIGEST "Do" /* the HTTP header for the manifest digest */ "cker-Content-Digest:"
-#define LAYERS_MAX 127
-
-static void dkr_pull_job_on_finished(PullJob *j);
-
-DkrPull* dkr_pull_unref(DkrPull *i) {
- if (!i)
- return NULL;
-
- if (i->tar_pid > 1) {
- (void) kill_and_sigcont(i->tar_pid, SIGKILL);
- (void) wait_for_terminate(i->tar_pid, NULL);
- }
-
- pull_job_unref(i->images_job);
- pull_job_unref(i->tags_job);
- pull_job_unref(i->ancestry_job);
- pull_job_unref(i->json_job);
- pull_job_unref(i->layer_job);
-
- curl_glue_unref(i->glue);
- sd_event_unref(i->event);
-
- if (i->temp_path) {
- (void) rm_rf(i->temp_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
- free(i->temp_path);
- }
-
- free(i->name);
- free(i->reference);
- free(i->id);
- free(i->response_token);
- strv_free(i->ancestry);
- free(i->final_path);
- free(i->index_address);
- free(i->index_protocol);
- free(i->index_url);
- free(i->image_root);
- free(i->local);
- free(i);
-
- return NULL;
-}
-
-int dkr_pull_new(
- DkrPull **ret,
- sd_event *event,
- const char *index_url,
- const char *image_root,
- DkrPullFinished on_finished,
- void *userdata) {
-
- _cleanup_(dkr_pull_unrefp) DkrPull *i = NULL;
- char *e;
- int r;
-
- assert(ret);
- assert(index_url);
-
- if (!http_url_is_valid(index_url))
- return -EINVAL;
-
- i = new0(DkrPull, 1);
- if (!i)
- return -ENOMEM;
-
- i->on_finished = on_finished;
- i->userdata = userdata;
-
- i->image_root = strdup(image_root ?: "/var/lib/machines");
- if (!i->image_root)
- return -ENOMEM;
-
- i->grow_machine_directory = path_startswith(i->image_root, "/var/lib/machines");
-
- i->index_url = strdup(index_url);
- if (!i->index_url)
- return -ENOMEM;
-
- e = endswith(i->index_url, "/");
- if (e)
- *e = 0;
-
- if (event)
- i->event = sd_event_ref(event);
- else {
- r = sd_event_default(&i->event);
- if (r < 0)
- return r;
- }
-
- r = curl_glue_new(&i->glue, i->event);
- if (r < 0)
- return r;
-
- i->glue->on_finished = pull_job_curl_on_finished;
- i->glue->userdata = i;
-
- *ret = i;
- i = NULL;
-
- return 0;
-}
-
-static void dkr_pull_report_progress(DkrPull *i, DkrProgress p) {
- unsigned percent;
-
- assert(i);
-
- switch (p) {
-
- case DKR_SEARCHING:
- percent = 0;
- if (i->images_job)
- percent += i->images_job->progress_percent * 5 / 100;
- break;
-
- case DKR_RESOLVING:
- percent = 5;
- if (i->tags_job)
- percent += i->tags_job->progress_percent * 5 / 100;
- break;
-
- case DKR_METADATA:
- percent = 10;
- if (i->ancestry_job)
- percent += i->ancestry_job->progress_percent * 5 / 100;
- if (i->json_job)
- percent += i->json_job->progress_percent * 5 / 100;
- break;
-
- case DKR_DOWNLOADING:
- percent = 20;
- percent += 75 * i->current_ancestry / MAX(1U, i->n_ancestry);
- if (i->layer_job)
- percent += i->layer_job->progress_percent * 75 / MAX(1U, i->n_ancestry) / 100;
-
- break;
-
- case DKR_COPYING:
- percent = 95;
- break;
-
- default:
- assert_not_reached("Unknown progress state");
- }
-
- sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent);
- log_debug("Combined progress %u%%", percent);
-}
-
-static int parse_id(const void *payload, size_t size, char **ret) {
- _cleanup_free_ char *buf = NULL, *id = NULL, *other = NULL;
- union json_value v = {};
- void *json_state = NULL;
- const char *p;
- int t;
-
- assert(payload);
- assert(ret);
-
- if (size <= 0)
- return -EBADMSG;
-
- if (memchr(payload, 0, size))
- return -EBADMSG;
-
- buf = strndup(payload, size);
- if (!buf)
- return -ENOMEM;
-
- p = buf;
- t = json_tokenize(&p, &id, &v, &json_state, NULL);
- if (t < 0)
- return t;
- if (t != JSON_STRING)
- return -EBADMSG;
-
- t = json_tokenize(&p, &other, &v, &json_state, NULL);
- if (t < 0)
- return t;
- if (t != JSON_END)
- return -EBADMSG;
-
- if (!dkr_id_is_valid(id))
- return -EBADMSG;
-
- *ret = id;
- id = NULL;
-
- return 0;
-}
-
-static int parse_ancestry(const void *payload, size_t size, char ***ret) {
- _cleanup_free_ char *buf = NULL;
- void *json_state = NULL;
- const char *p;
- enum {
- STATE_BEGIN,
- STATE_ITEM,
- STATE_COMMA,
- STATE_END,
- } state = STATE_BEGIN;
- _cleanup_strv_free_ char **l = NULL;
- size_t n = 0, allocated = 0;
-
- if (size <= 0)
- return -EBADMSG;
-
- if (memchr(payload, 0, size))
- return -EBADMSG;
-
- buf = strndup(payload, size);
- if (!buf)
- return -ENOMEM;
-
- p = buf;
- for (;;) {
- _cleanup_free_ char *str;
- union json_value v = {};
- int t;
-
- t = json_tokenize(&p, &str, &v, &json_state, NULL);
- if (t < 0)
- return t;
-
- switch (state) {
-
- case STATE_BEGIN:
- if (t == JSON_ARRAY_OPEN)
- state = STATE_ITEM;
- else
- return -EBADMSG;
-
- break;
-
- case STATE_ITEM:
- if (t == JSON_STRING) {
- if (!dkr_id_is_valid(str))
- return -EBADMSG;
-
- if (n+1 > LAYERS_MAX)
- return -EFBIG;
-
- if (!GREEDY_REALLOC(l, allocated, n + 2))
- return -ENOMEM;
-
- l[n++] = str;
- str = NULL;
- l[n] = NULL;
-
- state = STATE_COMMA;
-
- } else if (t == JSON_ARRAY_CLOSE)
- state = STATE_END;
- else
- return -EBADMSG;
-
- break;
-
- case STATE_COMMA:
- if (t == JSON_COMMA)
- state = STATE_ITEM;
- else if (t == JSON_ARRAY_CLOSE)
- state = STATE_END;
- else
- return -EBADMSG;
- break;
-
- case STATE_END:
- if (t == JSON_END) {
-
- if (strv_isempty(l))
- return -EBADMSG;
-
- if (!strv_is_uniq(l))
- return -EBADMSG;
-
- l = strv_reverse(l);
-
- *ret = l;
- l = NULL;
- return 0;
- } else
- return -EBADMSG;
- }
-
- }
-}
-
-static const char *dkr_pull_current_layer(DkrPull *i) {
- assert(i);
-
- if (strv_isempty(i->ancestry))
- return NULL;
-
- return i->ancestry[i->current_ancestry];
-}
-
-static const char *dkr_pull_current_base_layer(DkrPull *i) {
- assert(i);
-
- if (strv_isempty(i->ancestry))
- return NULL;
-
- if (i->current_ancestry <= 0)
- return NULL;
-
- return i->ancestry[i->current_ancestry-1];
-}
-
-static int dkr_pull_add_token(DkrPull *i, PullJob *j) {
- const char *t;
-
- assert(i);
- assert(j);
-
- if (i->response_token)
- t = strjoina("Authorization: Token ", i->response_token);
- else
- t = HEADER_TOKEN " true";
-
- j->request_header = curl_slist_new("Accept: application/json", t, NULL);
- if (!j->request_header)
- return -ENOMEM;
-
- return 0;
-}
-
-static int dkr_pull_add_bearer_token(DkrPull *i, PullJob *j) {
- const char *t = NULL;
-
- assert(i);
- assert(j);
-
- if (i->response_token)
- t = strjoina("Authorization: Bearer ", i->response_token);
- else
- return -EINVAL;
-
- j->request_header = curl_slist_new("Accept: application/json", t, NULL);
- if (!j->request_header)
- return -ENOMEM;
-
- return 0;
-}
-
-static bool dkr_pull_is_done(DkrPull *i) {
- assert(i);
- assert(i->images_job);
- if (i->images_job->state != PULL_JOB_DONE)
- return false;
-
- if (!i->tags_job || i->tags_job->state != PULL_JOB_DONE)
- return false;
-
- if (!i->ancestry_job || i->ancestry_job->state != PULL_JOB_DONE)
- return false;
-
- if (i->json_job && i->json_job->state != PULL_JOB_DONE)
- return false;
-
- if (i->layer_job && i->layer_job->state != PULL_JOB_DONE)
- return false;
-
- if (dkr_pull_current_layer(i))
- return false;
-
- return true;
-}
-
-static int dkr_pull_make_local_copy(DkrPull *i, DkrPullVersion version) {
- int r;
- _cleanup_free_ char *p = NULL;
-
- assert(i);
-
- if (!i->local)
- return 0;
-
- if (!i->final_path) {
- i->final_path = strjoin(i->image_root, "/.dkr-", i->id, NULL);
- if (!i->final_path)
- return -ENOMEM;
- }
-
- if (version == DKR_PULL_V2) {
- p = dirname_malloc(i->image_root);
- if (!p)
- return -ENOMEM;
- }
-
- r = pull_make_local_copy(i->final_path, p ?: i->image_root, i->local, i->force_local);
- if (r < 0)
- return r;
-
- if (version == DKR_PULL_V2) {
- char **k;
-
- STRV_FOREACH(k, i->ancestry) {
- _cleanup_free_ char *d;
-
- d = strjoin(i->image_root, "/.dkr-", *k, NULL);
- if (!d)
- return -ENOMEM;
-
- r = btrfs_subvol_remove(d, BTRFS_REMOVE_QUOTA);
- if (r < 0)
- return r;
- }
-
- r = rmdir(i->image_root);
- if (r < 0)
- return r;
- }
-
- return 0;
-}
-
-static int dkr_pull_job_on_open_disk(PullJob *j) {
- const char *base;
- DkrPull *i;
- int r;
-
- assert(j);
- assert(j->userdata);
-
- i = j->userdata;
- assert(i->layer_job == j);
- assert(i->final_path);
- assert(!i->temp_path);
- assert(i->tar_pid <= 0);
-
- r = tempfn_random(i->final_path, NULL, &i->temp_path);
- if (r < 0)
- return log_oom();
-
- mkdir_parents_label(i->temp_path, 0700);
-
- base = dkr_pull_current_base_layer(i);
- if (base) {
- const char *base_path;
-
- base_path = strjoina(i->image_root, "/.dkr-", base);
- r = btrfs_subvol_snapshot(base_path, i->temp_path, BTRFS_SNAPSHOT_FALLBACK_COPY|BTRFS_SNAPSHOT_QUOTA);
- } else
- r = btrfs_subvol_make(i->temp_path);
- if (r < 0)
- return log_error_errno(r, "Failed to make btrfs subvolume %s: %m", i->temp_path);
-
- (void) import_assign_pool_quota_and_warn(i->temp_path);
-
- j->disk_fd = import_fork_tar_x(i->temp_path, &i->tar_pid);
- if (j->disk_fd < 0)
- return j->disk_fd;
-
- return 0;
-}
-
-static void dkr_pull_job_on_progress(PullJob *j) {
- DkrPull *i;
-
- assert(j);
- assert(j->userdata);
-
- i = j->userdata;
-
- dkr_pull_report_progress(
- i,
- j == i->images_job ? DKR_SEARCHING :
- j == i->tags_job ? DKR_RESOLVING :
- j == i->ancestry_job || j == i->json_job ? DKR_METADATA :
- DKR_DOWNLOADING);
-}
-
-static void dkr_pull_job_on_finished_v2(PullJob *j);
-
-static int dkr_pull_pull_layer_v2(DkrPull *i) {
- _cleanup_free_ char *path = NULL;
- const char *url, *layer = NULL;
- int r;
-
- assert(i);
- assert(!i->layer_job);
- assert(!i->temp_path);
- assert(!i->final_path);
-
- for (;;) {
- layer = dkr_pull_current_layer(i);
- if (!layer)
- return 0; /* no more layers */
-
- path = strjoin(i->image_root, "/.dkr-", layer, NULL);
- if (!path)
- return log_oom();
-
- if (laccess(path, F_OK) < 0) {
- if (errno == ENOENT)
- break;
-
- return log_error_errno(errno, "Failed to check for container: %m");
- }
-
- log_info("Layer %s already exists, skipping.", layer);
-
- i->current_ancestry++;
-
- path = mfree(path);
- }
-
- log_info("Pulling layer %s...", layer);
-
- i->final_path = path;
- path = NULL;
-
- url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v2/", i->name, "/blobs/", layer);
- r = pull_job_new(&i->layer_job, url, i->glue, i);
- if (r < 0)
- return log_error_errno(r, "Failed to allocate layer job: %m");
-
- r = dkr_pull_add_bearer_token(i, i->layer_job);
- if (r < 0)
- return log_oom();
-
- i->layer_job->on_finished = dkr_pull_job_on_finished_v2;
- i->layer_job->on_open_disk = dkr_pull_job_on_open_disk;
- i->layer_job->on_progress = dkr_pull_job_on_progress;
- i->layer_job->grow_machine_directory = i->grow_machine_directory;
-
- r = pull_job_begin(i->layer_job);
- if (r < 0)
- return log_error_errno(r, "Failed to start layer job: %m");
-
- return 0;
-}
-
-static int dkr_pull_pull_layer(DkrPull *i) {
- _cleanup_free_ char *path = NULL;
- const char *url, *layer = NULL;
- int r;
-
- assert(i);
- assert(!i->layer_job);
- assert(!i->temp_path);
- assert(!i->final_path);
-
- for (;;) {
- layer = dkr_pull_current_layer(i);
- if (!layer)
- return 0; /* no more layers */
-
- path = strjoin(i->image_root, "/.dkr-", layer, NULL);
- if (!path)
- return log_oom();
-
- if (laccess(path, F_OK) < 0) {
- if (errno == ENOENT)
- break;
-
- return log_error_errno(errno, "Failed to check for container: %m");
- }
-
- log_info("Layer %s already exists, skipping.", layer);
-
- i->current_ancestry++;
-
- path = mfree(path);
- }
-
- log_info("Pulling layer %s...", layer);
-
- i->final_path = path;
- path = NULL;
-
- url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", layer, "/layer");
- r = pull_job_new(&i->layer_job, url, i->glue, i);
- if (r < 0)
- return log_error_errno(r, "Failed to allocate layer job: %m");
-
- r = dkr_pull_add_token(i, i->layer_job);
- if (r < 0)
- return log_oom();
-
- i->layer_job->on_finished = dkr_pull_job_on_finished;
- i->layer_job->on_open_disk = dkr_pull_job_on_open_disk;
- i->layer_job->on_progress = dkr_pull_job_on_progress;
- i->layer_job->grow_machine_directory = i->grow_machine_directory;
-
- r = pull_job_begin(i->layer_job);
- if (r < 0)
- return log_error_errno(r, "Failed to start layer job: %m");
-
- return 0;
-}
-
-static int dkr_pull_job_on_header(PullJob *j, const char *header, size_t sz) {
- _cleanup_free_ char *registry = NULL;
- char *token, *digest;
- DkrPull *i;
- int r;
-
- assert(j);
- assert(j->userdata);
-
- i = j->userdata;
- r = curl_header_strdup(header, sz, HEADER_TOKEN, &token);
- if (r < 0)
- return log_oom();
- if (r > 0) {
- free(i->response_token);
- i->response_token = token;
- return 0;
- }
-
- r = curl_header_strdup(header, sz, HEADER_DIGEST, &digest);
- if (r < 0)
- return log_oom();
- if (r > 0) {
- free(i->response_digest);
- i->response_digest = digest;
- return 0;
- }
-
- r = curl_header_strdup(header, sz, HEADER_REGISTRY, &registry);
- if (r < 0)
- return log_oom();
- if (r > 0) {
- char **l, **k;
-
- l = strv_split(registry, ",");
- if (!l)
- return log_oom();
-
- STRV_FOREACH(k, l) {
- if (!hostname_is_valid(*k, false)) {
- log_error("Registry hostname is not valid.");
- strv_free(l);
- return -EBADMSG;
- }
- }
-
- strv_free(i->response_registries);
- i->response_registries = l;
- }
-
- return 0;
-}
-
-static void dkr_pull_job_on_finished_v2(PullJob *j) {
- DkrPull *i;
- int r;
-
- assert(j);
- assert(j->userdata);
-
- i = j->userdata;
- if (j->error != 0) {
- if (j == i->images_job)
- log_error_errno(j->error, "Failed to retrieve images list. (Wrong index URL?)");
- else if (j == i->ancestry_job)
- log_error_errno(j->error, "Failed to retrieve manifest.");
- else if (j == i->json_job)
- log_error_errno(j->error, "Failed to retrieve json data.");
- else
- log_error_errno(j->error, "Failed to retrieve layer data.");
-
- r = j->error;
- goto finish;
- }
-
- if (i->images_job == j) {
- const char *url;
-
- assert(!i->tags_job);
- assert(!i->ancestry_job);
- assert(!i->json_job);
- assert(!i->layer_job);
-
- if (strv_isempty(i->response_registries)) {
- r = -EBADMSG;
- log_error("Didn't get registry information.");
- goto finish;
- }
-
- log_info("Index lookup succeeded, directed to registry %s.", i->response_registries[0]);
- dkr_pull_report_progress(i, DKR_RESOLVING);
-
- url = strjoina(i->index_protocol, "auth.", i->index_address, "/v2/token/?scope=repository:",
- i->name, ":pull&service=registry.", i->index_address);
- r = pull_job_new(&i->tags_job, url, i->glue, i);
- if (r < 0) {
- log_error_errno(r, "Failed to allocate tags job: %m");
- goto finish;
- }
-
- i->tags_job->on_finished = dkr_pull_job_on_finished_v2;
- i->tags_job->on_progress = dkr_pull_job_on_progress;
-
- r = pull_job_begin(i->tags_job);
- if (r < 0) {
- log_error_errno(r, "Failed to start tags job: %m");
- goto finish;
- }
-
- } else if (i->tags_job == j) {
- const char *url;
- _cleanup_free_ char *buf;
- _cleanup_json_variant_unref_ JsonVariant *doc = NULL;
- JsonVariant *e = NULL;
-
- assert(!i->ancestry_job);
- assert(!i->json_job);
- assert(!i->layer_job);
-
- buf = strndup((const char *)j->payload, j->payload_size);
- if (!buf) {
- r = -ENOMEM;
- log_oom();
- goto finish;
- }
-
- r = json_parse(buf, &doc);
- if (r < 0) {
- log_error("Unable to parse bearer token\n%s", j->payload);
- goto finish;
- }
-
- e = json_variant_value(doc, "token");
- if (!e || e->type != JSON_VARIANT_STRING) {
- r = -EBADMSG;
- log_error("Invalid JSON format for Bearer token");
- goto finish;
- }
-
- r = free_and_strdup(&i->response_token, json_variant_string(e));
- if (r < 0) {
- log_oom();
- goto finish;
- }
-
- url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v2/", i->name, "/manifests/", i->reference);
- r = pull_job_new(&i->ancestry_job, url, i->glue, i);
- if (r < 0) {
- log_error_errno(r, "Failed to allocate ancestry job: %m");
- goto finish;
- }
-
- r = dkr_pull_add_bearer_token(i, i->ancestry_job);
- if (r < 0)
- goto finish;
-
- i->ancestry_job->on_finished = dkr_pull_job_on_finished_v2;
- i->ancestry_job->on_progress = dkr_pull_job_on_progress;
- i->ancestry_job->on_header = dkr_pull_job_on_header;
-
-
- r = pull_job_begin(i->ancestry_job);
- if (r < 0) {
- log_error_errno(r, "Failed to start ancestry job: %m");
- goto finish;
- }
-
- } else if (i->ancestry_job == j) {
-
- _cleanup_json_variant_unref_ JsonVariant *doc = NULL, *compat = NULL;
- JsonVariant *e = NULL;
- _cleanup_strv_free_ char **ancestry = NULL;
- size_t allocated = 0, size = 0;
- char *path = NULL, **k = NULL;
-
- r = json_parse((const char *)j->payload, &doc);
- if (r < 0) {
- log_error("Invalid JSON Manifest");
- goto finish;
- }
-
- e = json_variant_value(doc, "fsLayers");
- if (!e || e->type != JSON_VARIANT_ARRAY || e->size == 0) {
- r = -EBADMSG;
- goto finish;
- }
-
- log_info("JSON manifest with schema v%"PRIi64" for %s parsed!",
- json_variant_integer(json_variant_value(doc, "schemaVersion")),
- json_variant_string(json_variant_value(doc, "name")));
-
- for (unsigned z = 0; z < e->size; z++) {
- JsonVariant *f = json_variant_element(e, z), *g = NULL;
- const char *layer;
- if (f->type != JSON_VARIANT_OBJECT) {
- r = -EBADMSG;
- goto finish;
- }
-
- g = json_variant_value(f, "blobSum");
-
- layer = json_variant_string(g);
- if (!dkr_digest_is_valid(layer)) {
- r = -EBADMSG;
- goto finish;
- }
-
- if (!GREEDY_REALLOC(ancestry, allocated, size + 2)) {
- r = -ENOMEM;
- log_oom();
- goto finish;
- }
-
- ancestry[size] = strdup(layer);
- if (!ancestry[size]) {
- r = -ENOMEM;
- log_oom();
- goto finish;
- }
-
- ancestry[size+1] = NULL;
- size += 1;
- }
-
- e = json_variant_value(doc, "history");
- if (!e || e->type != JSON_VARIANT_ARRAY) {
- r = -EBADMSG;
- goto finish;
- }
-
- e = json_variant_element(e, 0);
- e = json_variant_value(e, "v1Compatibility");
- r = json_parse(json_variant_string(e), &compat);
- if (r < 0) {
- log_error("Invalid v1Compatibility JSON");
- goto finish;
- }
-
- e = json_variant_value(compat, "id");
-
- strv_free(i->ancestry);
- i->ancestry = strv_reverse(strv_uniq(ancestry));
- i->n_ancestry = strv_length(i->ancestry);
- i->current_ancestry = 0;
- i->id = strdup(i->ancestry[i->n_ancestry - 1]);
- if (!i->id) {
- r = -ENOMEM;
- log_oom();
- goto finish;
- }
- path = strjoin(i->image_root, "/.dkr-", json_variant_string(e), NULL);
- if (!path) {
- r = -ENOMEM;
- log_oom();
- goto finish;
- }
- free(i->image_root);
- i->image_root = path;
- ancestry = NULL;
-
- log_info("Required layers:\n");
- STRV_FOREACH(k, i->ancestry)
- log_info("\t%s", *k);
- log_info("\nProvenance:\n\tImageID: %s\n\tDigest: %s", json_variant_string(e), i->response_digest);
-
- dkr_pull_report_progress(i, DKR_DOWNLOADING);
-
- r = dkr_pull_pull_layer_v2(i);
- if (r < 0)
- goto finish;
-
- } else if (i->layer_job == j) {
- assert(i->temp_path);
- assert(i->final_path);
-
- j->disk_fd = safe_close(j->disk_fd);
-
- if (i->tar_pid > 0) {
- r = wait_for_terminate_and_warn("tar", i->tar_pid, true);
- i->tar_pid = 0;
- if (r < 0)
- goto finish;
- }
-
- r = aufs_resolve(i->temp_path);
- if (r < 0) {
- log_error_errno(r, "Failed to resolve aufs whiteouts: %m");
- goto finish;
- }
-
- r = btrfs_subvol_set_read_only(i->temp_path, true);
- if (r < 0) {
- log_error_errno(r, "Failed to mark snapshot read-only: %m");
- goto finish;
- }
-
- if (rename(i->temp_path, i->final_path) < 0) {
- log_error_errno(errno, "Failed to rename snaphsot: %m");
- goto finish;
- }
-
- log_info("Completed writing to layer %s.", i->final_path);
-
- i->layer_job = pull_job_unref(i->layer_job);
- free(i->temp_path);
- i->temp_path = NULL;
- free(i->final_path);
- i->final_path = NULL;
-
- i->current_ancestry ++;
- r = dkr_pull_pull_layer_v2(i);
- if (r < 0)
- goto finish;
-
- } else if (i->json_job != j)
- assert_not_reached("Got finished event for unknown curl object");
-
- if (!dkr_pull_is_done(i))
- return;
-
- dkr_pull_report_progress(i, DKR_COPYING);
-
- r = dkr_pull_make_local_copy(i, DKR_PULL_V2);
- if (r < 0)
- goto finish;
-
- r = 0;
-
-finish:
- if (i->on_finished)
- i->on_finished(i, r, i->userdata);
- else
- sd_event_exit(i->event, r);
-
-}
-
-static void dkr_pull_job_on_finished(PullJob *j) {
- DkrPull *i;
- int r;
-
- assert(j);
- assert(j->userdata);
-
- i = j->userdata;
- if (j->error != 0) {
- if (j == i->images_job)
- log_error_errno(j->error, "Failed to retrieve images list. (Wrong index URL?)");
- else if (j == i->tags_job)
- log_error_errno(j->error, "Failed to retrieve tags list.");
- else if (j == i->ancestry_job)
- log_error_errno(j->error, "Failed to retrieve ancestry list.");
- else if (j == i->json_job)
- log_error_errno(j->error, "Failed to retrieve json data.");
- else
- log_error_errno(j->error, "Failed to retrieve layer data.");
-
- r = j->error;
- goto finish;
- }
-
- if (i->images_job == j) {
- const char *url;
-
- assert(!i->tags_job);
- assert(!i->ancestry_job);
- assert(!i->json_job);
- assert(!i->layer_job);
-
- if (strv_isempty(i->response_registries)) {
- r = -EBADMSG;
- log_error("Didn't get registry information.");
- goto finish;
- }
-
- log_info("Index lookup succeeded, directed to registry %s.", i->response_registries[0]);
- dkr_pull_report_progress(i, DKR_RESOLVING);
-
- url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/repositories/", i->name, "/tags/", i->reference);
- r = pull_job_new(&i->tags_job, url, i->glue, i);
- if (r < 0) {
- log_error_errno(r, "Failed to allocate tags job: %m");
- goto finish;
- }
-
- r = dkr_pull_add_token(i, i->tags_job);
- if (r < 0) {
- log_oom();
- goto finish;
- }
-
- i->tags_job->on_finished = dkr_pull_job_on_finished;
- i->tags_job->on_progress = dkr_pull_job_on_progress;
-
- r = pull_job_begin(i->tags_job);
- if (r < 0) {
- log_error_errno(r, "Failed to start tags job: %m");
- goto finish;
- }
-
- } else if (i->tags_job == j) {
- const char *url;
- char *id = NULL;
-
- assert(!i->ancestry_job);
- assert(!i->json_job);
- assert(!i->layer_job);
-
- r = parse_id(j->payload, j->payload_size, &id);
- if (r < 0) {
- log_error_errno(r, "Failed to parse JSON id.");
- goto finish;
- }
-
- free(i->id);
- i->id = id;
-
- log_info("Tag lookup succeeded, resolved to layer %s.", i->id);
- dkr_pull_report_progress(i, DKR_METADATA);
-
- url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", i->id, "/ancestry");
- r = pull_job_new(&i->ancestry_job, url, i->glue, i);
- if (r < 0) {
- log_error_errno(r, "Failed to allocate ancestry job: %m");
- goto finish;
- }
-
- r = dkr_pull_add_token(i, i->ancestry_job);
- if (r < 0) {
- log_oom();
- goto finish;
- }
-
- i->ancestry_job->on_finished = dkr_pull_job_on_finished;
- i->ancestry_job->on_progress = dkr_pull_job_on_progress;
-
- url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", i->id, "/json");
- r = pull_job_new(&i->json_job, url, i->glue, i);
- if (r < 0) {
- log_error_errno(r, "Failed to allocate json job: %m");
- goto finish;
- }
-
- r = dkr_pull_add_token(i, i->json_job);
- if (r < 0) {
- log_oom();
- goto finish;
- }
-
- i->json_job->on_finished = dkr_pull_job_on_finished;
- i->json_job->on_progress = dkr_pull_job_on_progress;
-
- r = pull_job_begin(i->ancestry_job);
- if (r < 0) {
- log_error_errno(r, "Failed to start ancestry job: %m");
- goto finish;
- }
-
- r = pull_job_begin(i->json_job);
- if (r < 0) {
- log_error_errno(r, "Failed to start json job: %m");
- goto finish;
- }
-
- } else if (i->ancestry_job == j) {
- char **ancestry = NULL, **k;
- unsigned n;
-
- assert(!i->layer_job);
-
- r = parse_ancestry(j->payload, j->payload_size, &ancestry);
- if (r < 0) {
- log_error_errno(r, "Failed to parse JSON id.");
- goto finish;
- }
-
- n = strv_length(ancestry);
- if (n <= 0 || !streq(ancestry[n-1], i->id)) {
- log_error("Ancestry doesn't end in main layer.");
- strv_free(ancestry);
- r = -EBADMSG;
- goto finish;
- }
-
- log_info("Ancestor lookup succeeded, requires layers:\n");
- STRV_FOREACH(k, ancestry)
- log_info("\t%s", *k);
-
- strv_free(i->ancestry);
- i->ancestry = ancestry;
- i->n_ancestry = n;
- i->current_ancestry = 0;
-
- dkr_pull_report_progress(i, DKR_DOWNLOADING);
-
- r = dkr_pull_pull_layer(i);
- if (r < 0)
- goto finish;
-
- } else if (i->layer_job == j) {
- assert(i->temp_path);
- assert(i->final_path);
-
- j->disk_fd = safe_close(j->disk_fd);
-
- if (i->tar_pid > 0) {
- r = wait_for_terminate_and_warn("tar", i->tar_pid, true);
- i->tar_pid = 0;
- if (r < 0)
- goto finish;
- }
-
- r = aufs_resolve(i->temp_path);
- if (r < 0) {
- log_error_errno(r, "Failed to resolve aufs whiteouts: %m");
- goto finish;
- }
-
- r = btrfs_subvol_set_read_only(i->temp_path, true);
- if (r < 0) {
- log_error_errno(r, "Failed to mark snapshot read-only: %m");
- goto finish;
- }
-
- if (rename(i->temp_path, i->final_path) < 0) {
- log_error_errno(errno, "Failed to rename snaphsot: %m");
- goto finish;
- }
-
- log_info("Completed writing to layer %s.", i->final_path);
-
- i->layer_job = pull_job_unref(i->layer_job);
- i->temp_path = mfree(i->temp_path);
- i->final_path = mfree(i->final_path);
-
- i->current_ancestry ++;
- r = dkr_pull_pull_layer(i);
- if (r < 0)
- goto finish;
-
- } else if (i->json_job != j)
- assert_not_reached("Got finished event for unknown curl object");
-
- if (!dkr_pull_is_done(i))
- return;
-
- dkr_pull_report_progress(i, DKR_COPYING);
-
- r = dkr_pull_make_local_copy(i, DKR_PULL_V1);
- if (r < 0)
- goto finish;
-
- r = 0;
-finish:
- if (i->on_finished)
- i->on_finished(i, r, i->userdata);
- else
- sd_event_exit(i->event, r);
-}
-
-static int get_protocol_address(char **protocol, char **address, const char *url) {
- const char *sep, *dot;
- _cleanup_free_ char *a = NULL, *p = NULL;
-
- sep = strstr(url, "://");
- if (!sep)
- return -EINVAL;
-
- dot = strrchr(url, '.');
- if (!dot)
- return -EINVAL;
- dot--;
-
- p = strndup(url, (sep - url) + 3);
- if (!p)
- return log_oom();
-
- while (dot > (sep + 3) && *dot != '.')
- dot--;
-
- a = strdup(dot + 1);
- if (!a)
- return log_oom();
-
- *address = a;
- *protocol = p;
- a = p = NULL;
-
- return 0;
-}
-
-int dkr_pull_start(DkrPull *i, const char *name, const char *reference, const char *local, bool force_local, DkrPullVersion version) {
- const char *url;
- int r;
-
- assert(i);
-
- if (!dkr_name_is_valid(name))
- return -EINVAL;
-
- if (reference && !dkr_ref_is_valid(reference))
- return -EINVAL;
-
- if (local && !machine_name_is_valid(local))
- return -EINVAL;
-
- if (i->images_job)
- return -EBUSY;
-
- if (!reference)
- reference = "latest";
-
- free(i->index_protocol);
- free(i->index_address);
- r = get_protocol_address(&i->index_protocol, &i->index_address, i->index_url);
- if (r < 0)
- return r;
-
- r = free_and_strdup(&i->local, local);
- if (r < 0)
- return r;
- i->force_local = force_local;
-
- r = free_and_strdup(&i->name, name);
- if (r < 0)
- return r;
- r = free_and_strdup(&i->reference, reference);
- if (r < 0)
- return r;
-
- url = strjoina(i->index_url, "/v1/repositories/", name, "/images");
-
- r = pull_job_new(&i->images_job, url, i->glue, i);
- if (r < 0)
- return r;
-
- r = dkr_pull_add_token(i, i->images_job);
- if (r < 0)
- return r;
-
- if (version == DKR_PULL_V1)
- i->images_job->on_finished = dkr_pull_job_on_finished;
- else
- i->images_job->on_finished = dkr_pull_job_on_finished_v2;
-
- i->images_job->on_header = dkr_pull_job_on_header;
- i->images_job->on_progress = dkr_pull_job_on_progress;
-
- return pull_job_begin(i->images_job);
-}
diff --git a/src/import/pull-dkr.h b/src/import/pull-dkr.h
deleted file mode 100644
index a95d91205b..0000000000
--- a/src/import/pull-dkr.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
-/***
- This file is part of systemd.
-
- Copyright 2014 Lennart Poettering
-
- systemd is free software; you can redistribute it and/or modify it
- under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation; either version 2.1 of the License, or
- (at your option) any later version.
-
- systemd is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License
- along with systemd; If not, see <http://www.gnu.org/licenses/>.
-***/
-
-#pragma once
-
-#include "sd-event.h"
-
-#include "util.h"
-
-typedef enum { DKR_PULL_V1, DKR_PULL_V2 } DkrPullVersion;
-typedef struct DkrPull DkrPull;
-
-typedef void (*DkrPullFinished)(DkrPull *pull, int error, void *userdata);
-
-int dkr_pull_new(DkrPull **pull, sd_event *event, const char *index_url, const char *image_root, DkrPullFinished on_finished, void *userdata);
-DkrPull* dkr_pull_unref(DkrPull *pull);
-
-DEFINE_TRIVIAL_CLEANUP_FUNC(DkrPull*, dkr_pull_unref);
-
-int dkr_pull_start(DkrPull *pull, const char *name, const char *tag, const char *local, bool force_local, DkrPullVersion version);
diff --git a/src/import/pull.c b/src/import/pull.c
index fc93228a0b..e0631bdeaf 100644
--- a/src/import/pull.c
+++ b/src/import/pull.c
@@ -28,7 +28,6 @@
#include "import-util.h"
#include "machine-image.h"
#include "parse-util.h"
-#include "pull-dkr.h"
#include "pull-raw.h"
#include "pull-tar.h"
#include "signal-util.h"
@@ -39,7 +38,6 @@
static bool arg_force = false;
static const char *arg_image_root = "/var/lib/machines";
static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE;
-static const char* arg_dkr_index_url = DEFAULT_DKR_INDEX_URL;
static bool arg_settings = true;
static int interrupt_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
@@ -220,114 +218,6 @@ static int pull_raw(int argc, char *argv[], void *userdata) {
return -r;
}
-static void on_dkr_finished(DkrPull *pull, int error, void *userdata) {
- sd_event *event = userdata;
- assert(pull);
-
- if (error == 0)
- log_info("Operation completed successfully.");
-
- sd_event_exit(event, abs(error));
-}
-
-static int pull_dkr(int argc, char *argv[], void *userdata) {
- _cleanup_(dkr_pull_unrefp) DkrPull *pull = NULL;
- _cleanup_(sd_event_unrefp) sd_event *event = NULL;
- const char *name, *reference, *local, *digest;
- int r;
-
- if (!arg_dkr_index_url) {
- log_error("Please specify an index URL with --dkr-index-url=");
- return -EINVAL;
- }
-
- if (arg_verify != IMPORT_VERIFY_NO) {
- log_error("Pulls from dkr do not support image verification, please pass --verify=no.");
- return -EINVAL;
- }
-
- digest = strchr(argv[1], '@');
- if (digest) {
- reference = digest + 1;
- name = strndupa(argv[1], digest - argv[1]);
- } else {
- reference = strchr(argv[1], ':');
- if (reference) {
- name = strndupa(argv[1], reference - argv[1]);
- reference++;
- } else {
- name = argv[1];
- reference = "latest";
- }
- }
-
- if (!dkr_name_is_valid(name)) {
- log_error("Remote name '%s' is not valid.", name);
- return -EINVAL;
- }
-
- if (!dkr_ref_is_valid(reference)) {
- log_error("Tag name '%s' is not valid.", reference);
- return -EINVAL;
- }
-
- if (argc >= 3)
- local = argv[2];
- else {
- local = strchr(name, '/');
- if (local)
- local++;
- else
- local = name;
- }
-
- if (isempty(local) || streq(local, "-"))
- local = NULL;
-
- if (local) {
- if (!machine_name_is_valid(local)) {
- log_error("Local image name '%s' is not valid.", local);
- return -EINVAL;
- }
-
- if (!arg_force) {
- r = image_find(local, NULL);
- if (r < 0)
- return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
- else if (r > 0) {
- log_error_errno(EEXIST, "Image '%s' already exists.", local);
- return -EEXIST;
- }
- }
-
- log_info("Pulling '%s' with reference '%s', saving as '%s'.", name, reference, local);
- } else
- log_info("Pulling '%s' with reference '%s'.", name, reference);
-
- r = sd_event_default(&event);
- if (r < 0)
- return log_error_errno(r, "Failed to allocate event loop: %m");
-
- assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0);
- (void) sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL);
- (void) sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL);
-
- r = dkr_pull_new(&pull, event, arg_dkr_index_url, arg_image_root, on_dkr_finished, event);
- if (r < 0)
- return log_error_errno(r, "Failed to allocate puller: %m");
-
- r = dkr_pull_start(pull, name, reference, local, arg_force, DKR_PULL_V2);
- if (r < 0)
- return log_error_errno(r, "Failed to pull image: %m");
-
- r = sd_event_loop(event);
- if (r < 0)
- return log_error_errno(r, "Failed to run event loop: %m");
-
- log_info("Exiting.");
- return -r;
-}
-
static int help(int argc, char *argv[], void *userdata) {
printf("%s [OPTIONS...] {COMMAND} ...\n\n"
@@ -338,12 +228,10 @@ static int help(int argc, char *argv[], void *userdata) {
" --verify=MODE Verify downloaded image, one of: 'no',\n"
" 'checksum', 'signature'\n"
" --settings=BOOL Download settings file with image\n"
- " --image-root=PATH Image root directory\n"
- " --dkr-index-url=URL Specify index URL to use for downloads\n\n"
+ " --image-root=PATH Image root directory\n\n"
"Commands:\n"
" tar URL [NAME] Download a TAR image\n"
- " raw URL [NAME] Download a RAW image\n"
- " dkr REMOTE [NAME] Download a DKR image\n",
+ " raw URL [NAME] Download a RAW image\n",
program_invocation_short_name);
return 0;
@@ -354,7 +242,6 @@ static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_FORCE,
- ARG_DKR_INDEX_URL,
ARG_IMAGE_ROOT,
ARG_VERIFY,
ARG_SETTINGS,
@@ -364,7 +251,6 @@ static int parse_argv(int argc, char *argv[]) {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "force", no_argument, NULL, ARG_FORCE },
- { "dkr-index-url", required_argument, NULL, ARG_DKR_INDEX_URL },
{ "image-root", required_argument, NULL, ARG_IMAGE_ROOT },
{ "verify", required_argument, NULL, ARG_VERIFY },
{ "settings", required_argument, NULL, ARG_SETTINGS },
@@ -390,15 +276,6 @@ static int parse_argv(int argc, char *argv[]) {
arg_force = true;
break;
- case ARG_DKR_INDEX_URL:
- if (!http_url_is_valid(optarg)) {
- log_error("Index URL is not valid: %s", optarg);
- return -EINVAL;
- }
-
- arg_dkr_index_url = optarg;
- break;
-
case ARG_IMAGE_ROOT:
arg_image_root = optarg;
break;
@@ -436,7 +313,6 @@ static int pull_main(int argc, char *argv[]) {
{ "help", VERB_ANY, VERB_ANY, 0, help },
{ "tar", 2, 3, 0, pull_tar },
{ "raw", 2, 3, 0, pull_raw },
- { "dkr", 2, 3, 0, pull_dkr },
{}
};
diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c
index 006791a542..4e96fb0a4d 100644
--- a/src/journal-remote/journal-gatewayd.c
+++ b/src/journal-remote/journal-gatewayd.c
@@ -45,6 +45,8 @@
#include "sigbus.h"
#include "util.h"
+#define JOURNAL_WAIT_TIMEOUT (10*USEC_PER_SEC)
+
static char *arg_key_pem = NULL;
static char *arg_cert_pem = NULL;
static char *arg_trust_pem = NULL;
@@ -181,11 +183,13 @@ static ssize_t request_reader_entries(
} else if (r == 0) {
if (m->follow) {
- r = sd_journal_wait(m->journal, (uint64_t) -1);
+ r = sd_journal_wait(m->journal, (uint64_t) JOURNAL_WAIT_TIMEOUT);
if (r < 0) {
log_error_errno(r, "Couldn't wait for journal event: %m");
return MHD_CONTENT_READER_END_WITH_ERROR;
}
+ if (r == SD_JOURNAL_NOP)
+ break;
continue;
}
@@ -241,6 +245,8 @@ static ssize_t request_reader_entries(
}
n = m->size - pos;
+ if (n < 1)
+ return 0;
if (n > max)
n = max;
diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c
index b2f5fbf6b4..3f93e85232 100644
--- a/src/journal-remote/journal-remote.c
+++ b/src/journal-remote/journal-remote.c
@@ -1181,6 +1181,7 @@ static DEFINE_CONFIG_PARSE_ENUM(config_parse_write_split_mode,
static int parse_config(void) {
const ConfigTableItem items[] = {
+ { "Remote", "Seal", config_parse_bool, 0, &arg_seal },
{ "Remote", "SplitMode", config_parse_write_split_mode, 0, &arg_split_mode },
{ "Remote", "ServerKeyFile", config_parse_path, 0, &arg_key },
{ "Remote", "ServerCertificateFile", config_parse_path, 0, &arg_cert },
diff --git a/src/journal-remote/journal-remote.conf.in b/src/journal-remote/journal-remote.conf.in
index 3e32f34def..7122d63362 100644
--- a/src/journal-remote/journal-remote.conf.in
+++ b/src/journal-remote/journal-remote.conf.in
@@ -1,4 +1,5 @@
[Remote]
+# Seal=false
# SplitMode=host
# ServerKeyFile=@CERTIFICATEROOT@/private/journal-remote.pem
# ServerCertificateFile=@CERTIFICATEROOT@/certs/journal-remote.pem
diff --git a/src/journal/compress.c b/src/journal/compress.c
index 1a3d2cdd80..1828165894 100644
--- a/src/journal/compress.c
+++ b/src/journal/compress.c
@@ -58,7 +58,8 @@ static const char* const object_compressed_table[_OBJECT_COMPRESSED_MAX] = {
DEFINE_STRING_TABLE_LOOKUP(object_compressed, int);
-int compress_blob_xz(const void *src, uint64_t src_size, void *dst, size_t *dst_size) {
+int compress_blob_xz(const void *src, uint64_t src_size,
+ void *dst, size_t dst_alloc_size, size_t *dst_size) {
#ifdef HAVE_XZ
static const lzma_options_lzma opt = {
1u << 20u, NULL, 0, LZMA_LC_DEFAULT, LZMA_LP_DEFAULT,
@@ -74,6 +75,7 @@ int compress_blob_xz(const void *src, uint64_t src_size, void *dst, size_t *dst_
assert(src);
assert(src_size > 0);
assert(dst);
+ assert(dst_alloc_size > 0);
assert(dst_size);
/* Returns < 0 if we couldn't compress the data or the
@@ -83,7 +85,7 @@ int compress_blob_xz(const void *src, uint64_t src_size, void *dst, size_t *dst_
return -ENOBUFS;
ret = lzma_stream_buffer_encode((lzma_filter*) filters, LZMA_CHECK_NONE, NULL,
- src, src_size, dst, &out_pos, src_size - 1);
+ src, src_size, dst, &out_pos, dst_alloc_size);
if (ret != LZMA_OK)
return -ENOBUFS;
@@ -94,13 +96,15 @@ int compress_blob_xz(const void *src, uint64_t src_size, void *dst, size_t *dst_
#endif
}
-int compress_blob_lz4(const void *src, uint64_t src_size, void *dst, size_t *dst_size) {
+int compress_blob_lz4(const void *src, uint64_t src_size,
+ void *dst, size_t dst_alloc_size, size_t *dst_size) {
#ifdef HAVE_LZ4
int r;
assert(src);
assert(src_size > 0);
assert(dst);
+ assert(dst_alloc_size > 0);
assert(dst_size);
/* Returns < 0 if we couldn't compress the data or the
@@ -109,7 +113,7 @@ int compress_blob_lz4(const void *src, uint64_t src_size, void *dst, size_t *dst
if (src_size < 9)
return -ENOBUFS;
- r = LZ4_compress_limitedOutput(src, dst + 8, src_size, src_size - 8 - 1);
+ r = LZ4_compress_limitedOutput(src, dst + 8, src_size, (int) dst_alloc_size - 8);
if (r <= 0)
return -ENOBUFS;
@@ -306,6 +310,7 @@ int decompress_startswith_lz4(const void *src, uint64_t src_size,
* prefix */
int r;
+ size_t size;
assert(src);
assert(src_size > 0);
@@ -322,10 +327,18 @@ int decompress_startswith_lz4(const void *src, uint64_t src_size,
r = LZ4_decompress_safe_partial(src + 8, *buffer, src_size - 8,
prefix_len + 1, *buffer_size);
+ if (r >= 0)
+ size = (unsigned) r;
+ else {
+ /* lz4 always tries to decode full "sequence", so in
+ * pathological cases might need to decompress the
+ * full field. */
+ r = decompress_blob_lz4(src, src_size, buffer, buffer_size, &size, 0);
+ if (r < 0)
+ return r;
+ }
- if (r < 0)
- return -EBADMSG;
- if ((unsigned) r >= prefix_len + 1)
+ if (size >= prefix_len + 1)
return memcmp(*buffer, prefix, prefix_len) == 0 &&
((const uint8_t*) *buffer)[prefix_len] == extra;
else
@@ -438,7 +451,7 @@ int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes) {
_cleanup_(LZ4F_freeCompressionContextp) LZ4F_compressionContext_t ctx = NULL;
_cleanup_free_ char *buf = NULL;
char *src = NULL;
- size_t size, n, total_in = 0, total_out = 0, offset = 0, frame_size;
+ size_t size, n, total_in = 0, total_out, offset = 0, frame_size;
struct stat st;
int r;
static const LZ4F_compressOptions_t options = {
@@ -461,7 +474,7 @@ int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes) {
if (!buf)
return -ENOMEM;
- n = offset = LZ4F_compressBegin(ctx, buf, size, &preferences);
+ n = offset = total_out = LZ4F_compressBegin(ctx, buf, size, &preferences);
if (LZ4F_isError(n))
return -EINVAL;
diff --git a/src/journal/compress.h b/src/journal/compress.h
index 9a065eb763..758598730a 100644
--- a/src/journal/compress.h
+++ b/src/journal/compress.h
@@ -28,17 +28,20 @@
const char* object_compressed_to_string(int compression);
int object_compressed_from_string(const char *compression);
-int compress_blob_xz(const void *src, uint64_t src_size, void *dst, size_t *dst_size);
-int compress_blob_lz4(const void *src, uint64_t src_size, void *dst, size_t *dst_size);
+int compress_blob_xz(const void *src, uint64_t src_size,
+ void *dst, size_t dst_alloc_size, size_t *dst_size);
+int compress_blob_lz4(const void *src, uint64_t src_size,
+ void *dst, size_t dst_alloc_size, size_t *dst_size);
-static inline int compress_blob(const void *src, uint64_t src_size, void *dst, size_t *dst_size) {
+static inline int compress_blob(const void *src, uint64_t src_size,
+ void *dst, size_t dst_alloc_size, size_t *dst_size) {
int r;
#ifdef HAVE_LZ4
- r = compress_blob_lz4(src, src_size, dst, dst_size);
+ r = compress_blob_lz4(src, src_size, dst, dst_alloc_size, dst_size);
if (r == 0)
return OBJECT_COMPRESSED_LZ4;
#else
- r = compress_blob_xz(src, src_size, dst, dst_size);
+ r = compress_blob_xz(src, src_size, dst, dst_alloc_size, dst_size);
if (r == 0)
return OBJECT_COMPRESSED_XZ;
#endif
diff --git a/src/journal/journal-file.c b/src/journal/journal-file.c
index f9ff9545dd..9e362bacae 100644
--- a/src/journal/journal-file.c
+++ b/src/journal/journal-file.c
@@ -169,8 +169,7 @@ JournalFile* journal_file_close(JournalFile *f) {
safe_close(f->fd);
free(f->path);
- if (f->mmap)
- mmap_cache_unref(f->mmap);
+ mmap_cache_unref(f->mmap);
ordered_hashmap_free_free(f->chain_cache);
@@ -1085,7 +1084,7 @@ static int journal_file_append_data(
if (JOURNAL_FILE_COMPRESS(f) && size >= COMPRESSION_SIZE_THRESHOLD) {
size_t rsize = 0;
- compression = compress_blob(data, size, o->data.payload, &rsize);
+ compression = compress_blob(data, size, o->data.payload, size - 1, &rsize);
if (compression >= 0) {
o->object.size = htole64(offsetof(Object, data.payload) + rsize);
diff --git a/src/journal/journald-native.c b/src/journal/journald-native.c
index 371df5b37f..f80a6ebfe5 100644
--- a/src/journal/journald-native.c
+++ b/src/journal/journald-native.c
@@ -495,5 +495,9 @@ int server_open_native_socket(Server*s) {
if (r < 0)
return log_error_errno(r, "Failed to add native server fd to event loop: %m");
+ r = sd_event_source_set_priority(s->native_event_source, SD_EVENT_PRIORITY_NORMAL+5);
+ if (r < 0)
+ return log_error_errno(r, "Failed to adjust native event source priority: %m");
+
return 0;
}
diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c
index 131fcdac42..90884b6929 100644
--- a/src/journal/journald-stream.c
+++ b/src/journal/journald-stream.c
@@ -733,7 +733,7 @@ int server_open_stdout_socket(Server *s) {
if (r < 0)
return log_error_errno(r, "Failed to add stdout server fd to event source: %m");
- r = sd_event_source_set_priority(s->stdout_event_source, SD_EVENT_PRIORITY_NORMAL+10);
+ r = sd_event_source_set_priority(s->stdout_event_source, SD_EVENT_PRIORITY_NORMAL+5);
if (r < 0)
return log_error_errno(r, "Failed to adjust priority of stdout server event source: %m");
diff --git a/src/journal/journald-syslog.c b/src/journal/journald-syslog.c
index cfc50d889b..0be73088e2 100644
--- a/src/journal/journald-syslog.c
+++ b/src/journal/journald-syslog.c
@@ -326,7 +326,7 @@ void server_process_syslog_message(
size_t label_len) {
char syslog_priority[sizeof("PRIORITY=") + DECIMAL_STR_MAX(int)],
- syslog_facility[sizeof("SYSLOG_FACILITY") + DECIMAL_STR_MAX(int)];
+ syslog_facility[sizeof("SYSLOG_FACILITY=") + DECIMAL_STR_MAX(int)];
const char *message = NULL, *syslog_identifier = NULL, *syslog_pid = NULL;
struct iovec iovec[N_IOVEC_META_FIELDS + 6];
unsigned n = 0;
@@ -357,11 +357,11 @@ void server_process_syslog_message(
IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=syslog");
- sprintf(syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK);
+ xsprintf(syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK);
IOVEC_SET_STRING(iovec[n++], syslog_priority);
if (priority & LOG_FACMASK) {
- sprintf(syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority));
+ xsprintf(syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority));
IOVEC_SET_STRING(iovec[n++], syslog_facility);
}
@@ -430,6 +430,10 @@ int server_open_syslog_socket(Server *s) {
if (r < 0)
return log_error_errno(r, "Failed to add syslog server fd to event loop: %m");
+ r = sd_event_source_set_priority(s->syslog_event_source, SD_EVENT_PRIORITY_NORMAL+5);
+ if (r < 0)
+ return log_error_errno(r, "Failed to adjust syslog event source priority: %m");
+
return 0;
}
diff --git a/src/journal/mmap-cache.c b/src/journal/mmap-cache.c
index 5a07ddda76..eb4b092e80 100644
--- a/src/journal/mmap-cache.c
+++ b/src/journal/mmap-cache.c
@@ -348,7 +348,10 @@ static void mmap_cache_free(MMapCache *m) {
}
MMapCache* mmap_cache_unref(MMapCache *m) {
- assert(m);
+
+ if (!m)
+ return NULL;
+
assert(m->n_ref > 0);
m->n_ref --;
diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c
index 5cde7f17f7..cd5160154a 100644
--- a/src/journal/sd-journal.c
+++ b/src/journal/sd-journal.c
@@ -1940,10 +1940,14 @@ _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void **
compression = o->object.flags & OBJECT_COMPRESSION_MASK;
if (compression) {
#if defined(HAVE_XZ) || defined(HAVE_LZ4)
- if (decompress_startswith(compression,
+ r = decompress_startswith(compression,
o->data.payload, l,
&f->compress_buffer, &f->compress_buffer_size,
- field, field_length, '=')) {
+ field, field_length, '=');
+ if (r < 0)
+ log_debug_errno(r, "Cannot decompress %s object of length %zu at offset "OFSfmt": %m",
+ object_compressed_to_string(compression), l, p);
+ else if (r > 0) {
size_t rsize;
diff --git a/src/journal/test-compress-benchmark.c b/src/journal/test-compress-benchmark.c
index 93ea9c6318..baed0d82a4 100644
--- a/src/journal/test-compress-benchmark.c
+++ b/src/journal/test-compress-benchmark.c
@@ -27,7 +27,8 @@
#include "string-util.h"
#include "util.h"
-typedef int (compress_t)(const void *src, uint64_t src_size, void *dst, size_t *dst_size);
+typedef int (compress_t)(const void *src, uint64_t src_size, void *dst,
+ size_t dst_alloc_size, size_t *dst_size);
typedef int (decompress_t)(const void *src, uint64_t src_size,
void **dst, size_t *dst_alloc_size, size_t* dst_size, size_t dst_max);
@@ -111,8 +112,8 @@ static void test_compress_decompress(const char* label, const char* type,
memzero(buf, MIN(size + 1000, MAX_SIZE));
- r = compress(text, size, buf, &j);
- /* assume compression must be successful except for small inputs */
+ r = compress(text, size, buf, size, &j);
+ /* assume compression must be successful except for small or random inputs */
assert_se(r == 0 || (size < 2048 && r == -ENOBUFS) || streq(type, "random"));
/* check for overwrites */
diff --git a/src/journal/test-compress.c b/src/journal/test-compress.c
index b9d90a8988..68c9a4d76c 100644
--- a/src/journal/test-compress.c
+++ b/src/journal/test-compress.c
@@ -17,6 +17,10 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
+#ifdef HAVE_LZ4
+#include <lz4.h>
+#endif
+
#include "alloc-util.h"
#include "compress.h"
#include "fd-util.h"
@@ -38,7 +42,7 @@
#endif
typedef int (compress_blob_t)(const void *src, uint64_t src_size,
- void *dst, size_t *dst_size);
+ void *dst, size_t dst_alloc_size, size_t *dst_size);
typedef int (decompress_blob_t)(const void *src, uint64_t src_size,
void **dst, size_t *dst_alloc_size,
size_t* dst_size, size_t dst_max);
@@ -57,15 +61,14 @@ static void test_compress_decompress(int compression,
size_t data_len,
bool may_fail) {
char compressed[512];
- size_t csize = 512;
- size_t usize = 0;
+ size_t csize, usize = 0;
_cleanup_free_ char *decompressed = NULL;
int r;
log_info("/* testing %s %s blob compression/decompression */",
object_compressed_to_string(compression), data);
- r = compress(data, data_len, compressed, &csize);
+ r = compress(data, data_len, compressed, sizeof(compressed), &csize);
if (r == -ENOBUFS) {
log_info_errno(r, "compression failed: %m");
assert_se(may_fail);
@@ -101,43 +104,45 @@ static void test_decompress_startswith(int compression,
size_t data_len,
bool may_fail) {
- char compressed[512];
- size_t csize = 512;
- size_t usize = 0;
- _cleanup_free_ char *decompressed = NULL;
+ char *compressed;
+ _cleanup_free_ char *compressed1 = NULL, *compressed2 = NULL, *decompressed = NULL;
+ size_t csize, usize = 0, len;
int r;
- log_info("/* testing decompress_startswith with %s on %s text*/",
+ log_info("/* testing decompress_startswith with %s on %.20s text*/",
object_compressed_to_string(compression), data);
- r = compress(data, data_len, compressed, &csize);
+#define BUFSIZE_1 512
+#define BUFSIZE_2 20000
+
+ compressed = compressed1 = malloc(BUFSIZE_1);
+ assert_se(compressed1);
+ r = compress(data, data_len, compressed, BUFSIZE_1, &csize);
if (r == -ENOBUFS) {
log_info_errno(r, "compression failed: %m");
assert_se(may_fail);
- return;
+
+ compressed = compressed2 = malloc(BUFSIZE_2);
+ assert_se(compressed2);
+ r = compress(data, data_len, compressed, BUFSIZE_2, &csize);
+ assert(r == 0);
}
assert_se(r == 0);
- assert_se(decompress_sw(compressed,
- csize,
- (void **) &decompressed,
- &usize,
- data, strlen(data), '\0') > 0);
- assert_se(decompress_sw(compressed,
- csize,
- (void **) &decompressed,
- &usize,
- data, strlen(data), 'w') == 0);
- assert_se(decompress_sw(compressed,
- csize,
- (void **) &decompressed,
- &usize,
- "barbarbar", 9, ' ') == 0);
- assert_se(decompress_sw(compressed,
- csize,
- (void **) &decompressed,
- &usize,
- data, strlen(data), '\0') > 0);
+ len = strlen(data);
+
+ r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len, '\0');
+ assert_se(r > 0);
+ r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len, 'w');
+ assert_se(r == 0);
+ r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, "barbarbar", 9, ' ');
+ assert_se(r == 0);
+ r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len - 1, data[len-1]);
+ assert_se(r > 0);
+ r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len - 1, 'w');
+ assert_se(r == 0);
+ r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len, '\0');
+ assert_se(r > 0);
}
static void test_compress_stream(int compression,
@@ -199,6 +204,44 @@ static void test_compress_stream(int compression,
assert_se(unlink(pattern2) == 0);
}
+#ifdef HAVE_LZ4
+static void test_lz4_decompress_partial(void) {
+ char buf[20000];
+ size_t buf_size = sizeof(buf), compressed;
+ int r;
+ _cleanup_free_ char *huge = NULL;
+
+#define HUGE_SIZE (4096*1024)
+ huge = malloc(HUGE_SIZE);
+ memset(huge, 'x', HUGE_SIZE);
+ memcpy(huge, "HUGE=", 5);
+
+ r = LZ4_compress_limitedOutput(huge, buf, HUGE_SIZE, buf_size);
+ assert_se(r >= 0);
+ compressed = r;
+ log_info("Compressed %i → %zu", HUGE_SIZE, compressed);
+
+ r = LZ4_decompress_safe(buf, huge, r, HUGE_SIZE);
+ assert_se(r >= 0);
+ log_info("Decompressed → %i", r);
+
+ r = LZ4_decompress_safe_partial(buf, huge,
+ compressed,
+ 12, HUGE_SIZE);
+ assert_se(r >= 0);
+ log_info("Decompressed partial %i/%i → %i", 12, HUGE_SIZE, r);
+
+ /* We expect this to fail, because that's how current lz4 works. If this
+ * call succeeds, then lz4 has been fixed, and we need to change our code.
+ */
+ r = LZ4_decompress_safe_partial(buf, huge,
+ compressed,
+ 12, HUGE_SIZE-1);
+ assert_se(r < 0);
+ log_info("Decompressed partial %i/%i → %i", 12, HUGE_SIZE-1, r);
+}
+#endif
+
int main(int argc, char *argv[]) {
const char text[] =
"text\0foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF"
@@ -206,6 +249,11 @@ int main(int argc, char *argv[]) {
char data[512] = "random\0";
+ char huge[4096*1024];
+ memset(huge, 'x', sizeof(huge));
+ memcpy(huge, "HUGE=", 5);
+ char_array_0(huge);
+
log_set_max_level(LOG_DEBUG);
random_bytes(data + 7, sizeof(data) - 7);
@@ -215,12 +263,17 @@ int main(int argc, char *argv[]) {
text, sizeof(text), false);
test_compress_decompress(OBJECT_COMPRESSED_XZ, compress_blob_xz, decompress_blob_xz,
data, sizeof(data), true);
+
test_decompress_startswith(OBJECT_COMPRESSED_XZ,
compress_blob_xz, decompress_startswith_xz,
text, sizeof(text), false);
test_decompress_startswith(OBJECT_COMPRESSED_XZ,
compress_blob_xz, decompress_startswith_xz,
data, sizeof(data), true);
+ test_decompress_startswith(OBJECT_COMPRESSED_XZ,
+ compress_blob_xz, decompress_startswith_xz,
+ huge, sizeof(huge), true);
+
test_compress_stream(OBJECT_COMPRESSED_XZ, "xzcat",
compress_stream_xz, decompress_stream_xz, argv[0]);
#else
@@ -232,15 +285,21 @@ int main(int argc, char *argv[]) {
text, sizeof(text), false);
test_compress_decompress(OBJECT_COMPRESSED_LZ4, compress_blob_lz4, decompress_blob_lz4,
data, sizeof(data), true);
+
test_decompress_startswith(OBJECT_COMPRESSED_LZ4,
compress_blob_lz4, decompress_startswith_lz4,
text, sizeof(text), false);
test_decompress_startswith(OBJECT_COMPRESSED_LZ4,
compress_blob_lz4, decompress_startswith_lz4,
data, sizeof(data), true);
+ test_decompress_startswith(OBJECT_COMPRESSED_LZ4,
+ compress_blob_lz4, decompress_startswith_lz4,
+ huge, sizeof(huge), true);
test_compress_stream(OBJECT_COMPRESSED_LZ4, "lz4cat",
compress_stream_lz4, decompress_stream_lz4, argv[0]);
+
+ test_lz4_decompress_partial();
#else
log_info("/* LZ4 test skipped */");
#endif
diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c
index e875ba4986..6fb80dda7a 100644
--- a/src/libsystemd-network/sd-dhcp-lease.c
+++ b/src/libsystemd-network/sd-dhcp-lease.c
@@ -37,6 +37,7 @@
#include "in-addr-util.h"
#include "network-internal.h"
#include "parse-util.h"
+#include "stdio-util.h"
#include "string-util.h"
#include "unaligned.h"
@@ -839,7 +840,7 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
LIST_FOREACH(options, option, lease->private_options) {
char key[strlen("OPTION_000")+1];
- snprintf(key, sizeof(key), "OPTION_%"PRIu8, option->tag);
+ xsprintf(key, "OPTION_%" PRIu8, option->tag);
r = serialize_dhcp_option(f, key, option->data, option->length);
if (r < 0)
goto fail;
diff --git a/src/libsystemd-network/sd-lldp.c b/src/libsystemd-network/sd-lldp.c
index d3ea74404b..1c696f9ef0 100644
--- a/src/libsystemd-network/sd-lldp.c
+++ b/src/libsystemd-network/sd-lldp.c
@@ -145,12 +145,9 @@ static int lldp_receive_frame(sd_lldp *lldp, tlv_packet *tlv) {
/* 10.3.2 LLDPDU validation: rxProcessFrame() */
int lldp_handle_packet(tlv_packet *tlv, uint16_t length) {
+ bool system_description = false, system_name = false, chassis_id = false;
+ bool malformed = false, port_id = false, ttl = false, end = false;
uint16_t type, len, i, l, t;
- bool chassis_id = false;
- bool malformed = false;
- bool port_id = false;
- bool ttl = false;
- bool end = false;
lldp_port *port;
uint8_t *p, *q;
sd_lldp *lldp;
@@ -163,8 +160,7 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) {
lldp = (sd_lldp *) port->userdata;
if (lldp->port->status == LLDP_PORT_STATUS_DISABLED) {
- log_lldp("Port is disabled : %s . Dropping ...",
- lldp->port->ifname);
+ log_lldp("Port: %s is disabled. Dropping.", lldp->port->ifname);
goto out;
}
@@ -182,8 +178,7 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) {
if (type == LLDP_TYPE_END) {
if (len != 0) {
- log_lldp("TLV type end is not length 0. Length:%d received . Dropping ...",
- len);
+ log_lldp("TLV type end must be length 0 (not %d). Dropping.", len);
malformed = true;
goto out;
@@ -193,8 +188,7 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) {
break;
} else if (type >=_LLDP_TYPE_MAX) {
- log_lldp("TLV type not recognized %d . Dropping ...",
- type);
+ log_lldp("TLV type: %d not recognized. Dropping.", type);
malformed = true;
goto out;
@@ -209,7 +203,7 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) {
if (i <= 3) {
if (i != type) {
- log_lldp("TLV missing or out of order. Dropping ...");
+ log_lldp("TLV missing or out of order. Dropping.");
malformed = true;
goto out;
@@ -220,25 +214,22 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) {
case LLDP_TYPE_CHASSIS_ID:
if (len < 2) {
- log_lldp("Received malformed Chassis ID TLV len = %d. Dropping",
- len);
+ log_lldp("Received malformed Chassis ID TLV length: %d. Dropping.", len);
malformed = true;
goto out;
}
if (chassis_id) {
- log_lldp("Duplicate Chassis ID TLV found. Dropping ...");
+ log_lldp("Duplicate Chassis ID TLV found. Dropping.");
malformed = true;
goto out;
}
/* Look what subtype it has */
- if (*q == LLDP_CHASSIS_SUBTYPE_RESERVED ||
- *q > LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED) {
- log_lldp("Unknown subtype: %d found in Chassis ID TLV . Dropping ...",
- *q);
+ if (*q == LLDP_CHASSIS_SUBTYPE_RESERVED || *q > LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED) {
+ log_lldp("Unknown subtype: %d found in Chassis ID TLV. Dropping.", *q);
malformed = true;
goto out;
@@ -251,25 +242,22 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) {
case LLDP_TYPE_PORT_ID:
if (len < 2) {
- log_lldp("Received malformed Port ID TLV len = %d. Dropping",
- len);
+ log_lldp("Received malformed Port ID TLV length: %d. Dropping.", len);
malformed = true;
goto out;
}
if (port_id) {
- log_lldp("Duplicate Port ID TLV found. Dropping ...");
+ log_lldp("Duplicate Port ID TLV found. Dropping.");
malformed = true;
goto out;
}
/* Look what subtype it has */
- if (*q == LLDP_PORT_SUBTYPE_RESERVED ||
- *q > LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED) {
- log_lldp("Unknown subtype: %d found in Port ID TLV . Dropping ...",
- *q);
+ if (*q == LLDP_PORT_SUBTYPE_RESERVED || *q > LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED) {
+ log_lldp("Unknown subtype: %d found in Port ID TLV. Dropping.", *q);
malformed = true;
goto out;
@@ -282,16 +270,14 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) {
case LLDP_TYPE_TTL:
if(len != 2) {
- log_lldp(
- "Received invalid lenth: %d TTL TLV. Dropping ...",
- len);
+ log_lldp("Received invalid TTL TLV lenth: %d. Dropping.", len);
malformed = true;
goto out;
}
if (ttl) {
- log_lldp("Duplicate TTL TLV found. Dropping ...");
+ log_lldp("Duplicate TTL TLV found. Dropping.");
malformed = true;
goto out;
@@ -300,11 +286,45 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) {
ttl = true;
break;
+ case LLDP_TYPE_SYSTEM_NAME:
+
+ /* According to RFC 1035 the length of a FQDN is limited to 255 characters */
+ if (len > 255) {
+ log_lldp("Received invalid system name length: %d. Dropping.", len);
+ malformed = true;
+ goto out;
+ }
+
+ if (system_name) {
+ log_lldp("Duplicate system name found. Dropping.");
+ malformed = true;
+ goto out;
+ }
+
+ system_name = true;
+
+ break;
+ case LLDP_TYPE_SYSTEM_DESCRIPTION:
+
+ /* 0 <= n <= 255 octets */
+ if (len > 255) {
+ log_lldp("Received invalid system description length: %d. Dropping.", len);
+ malformed = true;
+ goto out;
+ }
+
+ if (system_description) {
+ log_lldp("Duplicate system description found. Dropping.");
+ malformed = true;
+ goto out;
+ }
+
+ system_description = true;
+ break;
default:
if (len == 0) {
- log_lldp("TLV type = %d's, length 0 received . Dropping ...",
- type);
+ log_lldp("TLV type: %d length 0 received. Dropping.", type);
malformed = true;
goto out;
@@ -314,7 +334,7 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) {
}
if(!chassis_id || !port_id || !ttl || !end) {
- log_lldp( "One or more mandotory TLV missing . Dropping ...");
+ log_lldp("One or more mandatory TLV missing. Dropping.");
malformed = true;
goto out;
@@ -323,7 +343,7 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) {
r = tlv_packet_parse_pdu(tlv, length);
if (r < 0) {
- log_lldp( "Failed to parse the TLV. Dropping ...");
+ log_lldp("Failed to parse the TLV. Dropping.");
malformed = true;
goto out;
diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c
index 8d486fcbbd..6e2594d001 100644
--- a/src/libsystemd/sd-bus/bus-common-errors.c
+++ b/src/libsystemd/sd-bus/bus-common-errors.c
@@ -67,12 +67,14 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_PROCESS, ESRCH),
- SD_BUS_ERROR_MAP(BUS_ERROR_NO_NAME_SERVERS, EIO),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_NAME_SERVERS, ESRCH),
SD_BUS_ERROR_MAP(BUS_ERROR_INVALID_REPLY, EINVAL),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_RR, ENOENT),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_RESOURCES, ENOMEM),
SD_BUS_ERROR_MAP(BUS_ERROR_CNAME_LOOP, EDEADLK),
SD_BUS_ERROR_MAP(BUS_ERROR_ABORTED, ECANCELED),
+ SD_BUS_ERROR_MAP(BUS_ERROR_CONNECTION_FAILURE, ECONNREFUSED),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_SERVICE, EUNATCH),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_TRANSFER, ENXIO),
SD_BUS_ERROR_MAP(BUS_ERROR_TRANSFER_IN_PROGRESS, EBUSY),
diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h
index f2092795f4..7a5f6cda87 100644
--- a/src/libsystemd/sd-bus/bus-common-errors.h
+++ b/src/libsystemd/sd-bus/bus-common-errors.h
@@ -72,6 +72,11 @@
#define BUS_ERROR_NO_RESOURCES "org.freedesktop.resolve1.NoResources"
#define BUS_ERROR_CNAME_LOOP "org.freedesktop.resolve1.CNameLoop"
#define BUS_ERROR_ABORTED "org.freedesktop.resolve1.Aborted"
+#define BUS_ERROR_CONNECTION_FAILURE "org.freedesktop.resolve1.ConnectionFailure"
+#define BUS_ERROR_NO_SUCH_SERVICE "org.freedesktop.resolve1.NoSuchService"
+#define BUS_ERROR_DNSSEC_FAILED "org.freedesktop.resolve1.DnssecFailed"
+#define BUS_ERROR_NO_TRUST_ANCHOR "org.freedesktop.resolve1.NoTrustAnchor"
+#define BUS_ERROR_RR_TYPE_UNSUPPORTED "org.freedesktop.resolve1.ResourceRecordTypeUnsupported"
#define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError."
#define BUS_ERROR_NO_SUCH_TRANSFER "org.freedesktop.import1.NoSuchTransfer"
diff --git a/src/libsystemd/sd-bus/bus-kernel.c b/src/libsystemd/sd-bus/bus-kernel.c
index 6c05444e9a..b2d685855e 100644
--- a/src/libsystemd/sd-bus/bus-kernel.c
+++ b/src/libsystemd/sd-bus/bus-kernel.c
@@ -47,6 +47,7 @@
#include "formats-util.h"
#include "memfd-util.h"
#include "parse-util.h"
+#include "stdio-util.h"
#include "string-util.h"
#include "strv.h"
#include "user-util.h"
@@ -849,7 +850,8 @@ static int bus_kernel_make_message(sd_bus *bus, struct kdbus_msg *k) {
if (k->src_id == KDBUS_SRC_ID_KERNEL)
bus_message_set_sender_driver(bus, m);
else {
- snprintf(m->sender_buffer, sizeof(m->sender_buffer), ":1.%llu", (unsigned long long) k->src_id);
+ xsprintf(m->sender_buffer, ":1.%llu",
+ (unsigned long long)k->src_id);
m->sender = m->creds.unique_name = m->sender_buffer;
}
@@ -860,7 +862,8 @@ static int bus_kernel_make_message(sd_bus *bus, struct kdbus_msg *k) {
else if (k->dst_id == KDBUS_DST_ID_NAME)
m->destination = bus->unique_name; /* fill in unique name if the well-known name is missing */
else {
- snprintf(m->destination_buffer, sizeof(m->destination_buffer), ":1.%llu", (unsigned long long) k->dst_id);
+ xsprintf(m->destination_buffer, ":1.%llu",
+ (unsigned long long)k->dst_id);
m->destination = m->destination_buffer;
}
diff --git a/src/libsystemd/sd-bus/test-bus-cleanup.c b/src/libsystemd/sd-bus/test-bus-cleanup.c
index 1c3ccda364..cbc450fdb2 100644
--- a/src/libsystemd/sd-bus/test-bus-cleanup.c
+++ b/src/libsystemd/sd-bus/test-bus-cleanup.c
@@ -36,7 +36,7 @@ static void test_bus_new(void) {
}
static int test_bus_open(void) {
- _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r;
r = sd_bus_open_system(&bus);
@@ -59,8 +59,8 @@ static void test_bus_new_method_call(void) {
printf("after message_new_method_call: refcount %u\n", REFCNT_GET(bus->n_ref));
- sd_bus_unref(bus);
- printf("after bus_unref: refcount %u\n", m->n_ref);
+ sd_bus_flush_close_unref(bus);
+ printf("after bus_flush_close_unref: refcount %u\n", m->n_ref);
}
static void test_bus_new_signal(void) {
@@ -73,8 +73,8 @@ static void test_bus_new_signal(void) {
printf("after message_new_signal: refcount %u\n", REFCNT_GET(bus->n_ref));
- sd_bus_unref(bus);
- printf("after bus_unref: refcount %u\n", m->n_ref);
+ sd_bus_flush_close_unref(bus);
+ printf("after bus_flush_close_unref: refcount %u\n", m->n_ref);
}
int main(int argc, char **argv) {
diff --git a/src/libsystemd/sd-bus/test-bus-marshal.c b/src/libsystemd/sd-bus/test-bus-marshal.c
index 077cc6ddac..b9d1ea5217 100644
--- a/src/libsystemd/sd-bus/test-bus-marshal.c
+++ b/src/libsystemd/sd-bus/test-bus-marshal.c
@@ -246,6 +246,8 @@ int main(int argc, char *argv[]) {
log_error("%s", error.message);
else
dbus_message_unref(w);
+
+ dbus_error_free(&error);
}
#endif
diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c
index 3191b458d1..aeb06ad9a8 100644
--- a/src/libsystemd/sd-event/sd-event.c
+++ b/src/libsystemd/sd-event/sd-event.c
@@ -37,6 +37,7 @@
#include "process-util.h"
#include "set.h"
#include "signal-util.h"
+#include "string-table.h"
#include "string-util.h"
#include "time-util.h"
#include "util.h"
@@ -60,6 +61,23 @@ typedef enum EventSourceType {
_SOURCE_EVENT_SOURCE_TYPE_INVALID = -1
} EventSourceType;
+static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX] = {
+ [SOURCE_IO] = "io",
+ [SOURCE_TIME_REALTIME] = "realtime",
+ [SOURCE_TIME_BOOTTIME] = "bootime",
+ [SOURCE_TIME_MONOTONIC] = "monotonic",
+ [SOURCE_TIME_REALTIME_ALARM] = "realtime-alarm",
+ [SOURCE_TIME_BOOTTIME_ALARM] = "boottime-alarm",
+ [SOURCE_SIGNAL] = "signal",
+ [SOURCE_CHILD] = "child",
+ [SOURCE_DEFER] = "defer",
+ [SOURCE_POST] = "post",
+ [SOURCE_EXIT] = "exit",
+ [SOURCE_WATCHDOG] = "watchdog",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int);
+
/* All objects we use in epoll events start with this value, so that
* we know how to dispatch it */
typedef enum WakeupType {
@@ -207,6 +225,7 @@ struct sd_event {
bool exit_requested:1;
bool need_process_child:1;
bool watchdog:1;
+ bool profile_delays:1;
int exit_code;
@@ -218,6 +237,9 @@ struct sd_event {
unsigned n_sources;
LIST_HEAD(sd_event_source, sources);
+
+ usec_t last_run, last_log;
+ unsigned delays[sizeof(usec_t) * 8];
};
static void source_disconnect(sd_event_source *s);
@@ -426,6 +448,11 @@ _public_ int sd_event_new(sd_event** ret) {
goto fail;
}
+ if (secure_getenv("SD_EVENT_PROFILE_DELAYS")) {
+ log_debug("Event loop profiling enabled. Logarithmic histogram of event loop iterations in the range 2^0 ... 2^63 us will be logged every 5s.");
+ e->profile_delays = true;
+ }
+
*ret = e;
return 0;
@@ -482,7 +509,8 @@ static void source_io_unregister(sd_event_source *s) {
r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->io.fd, NULL);
if (r < 0)
- log_debug_errno(errno, "Failed to remove source %s from epoll: %m", strna(s->description));
+ log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll: %m",
+ strna(s->description), event_source_type_to_string(s->type));
s->io.registered = false;
}
@@ -2281,12 +2309,9 @@ static int source_dispatch(sd_event_source *s) {
s->dispatching = false;
- if (r < 0) {
- if (s->description)
- log_debug_errno(r, "Event source '%s' returned error, disabling: %m", s->description);
- else
- log_debug_errno(r, "Event source %p returned error, disabling: %m", s);
- }
+ if (r < 0)
+ log_debug_errno(r, "Event source %s (type %s) returned error, disabling: %m",
+ strna(s->description), event_source_type_to_string(s->type));
if (s->n_ref == 0)
source_free(s);
@@ -2319,12 +2344,9 @@ static int event_prepare(sd_event *e) {
r = s->prepare(s, s->userdata);
s->dispatching = false;
- if (r < 0) {
- if (s->description)
- log_debug_errno(r, "Prepare callback of event source '%s' returned error, disabling: %m", s->description);
- else
- log_debug_errno(r, "Prepare callback of event source %p returned error, disabling: %m", s);
- }
+ if (r < 0)
+ log_debug_errno(r, "Prepare callback of event source %s (type %s) returned error, disabling: %m",
+ strna(s->description), event_source_type_to_string(s->type));
if (s->n_ref == 0)
source_free(s);
@@ -2609,6 +2631,18 @@ _public_ int sd_event_dispatch(sd_event *e) {
return 1;
}
+static void event_log_delays(sd_event *e) {
+ char b[ELEMENTSOF(e->delays) * DECIMAL_STR_MAX(unsigned) + 1];
+ unsigned i;
+ int o;
+
+ for (i = o = 0; i < ELEMENTSOF(e->delays); i++) {
+ o += snprintf(&b[o], sizeof(b) - o, "%u ", e->delays[i]);
+ e->delays[i] = 0;
+ }
+ log_debug("Event loop iterations: %.*s", o, b);
+}
+
_public_ int sd_event_run(sd_event *e, uint64_t timeout) {
int r;
@@ -2617,11 +2651,30 @@ _public_ int sd_event_run(sd_event *e, uint64_t timeout) {
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
assert_return(e->state == SD_EVENT_INITIAL, -EBUSY);
+ if (e->profile_delays && e->last_run) {
+ usec_t this_run;
+ unsigned l;
+
+ this_run = now(CLOCK_MONOTONIC);
+
+ l = u64log2(this_run - e->last_run);
+ assert(l < sizeof(e->delays));
+ e->delays[l]++;
+
+ if (this_run - e->last_log >= 5*USEC_PER_SEC) {
+ event_log_delays(e);
+ e->last_log = this_run;
+ }
+ }
+
r = sd_event_prepare(e);
if (r == 0)
/* There was nothing? Then wait... */
r = sd_event_wait(e, timeout);
+ if (e->profile_delays)
+ e->last_run = now(CLOCK_MONOTONIC);
+
if (r > 0) {
/* There's something now, then let's dispatch it */
r = sd_event_dispatch(e);
diff --git a/src/libsystemd/sd-netlink/netlink-socket.c b/src/libsystemd/sd-netlink/netlink-socket.c
index 2181201017..e95c99af0d 100644
--- a/src/libsystemd/sd-netlink/netlink-socket.c
+++ b/src/libsystemd/sd-netlink/netlink-socket.c
@@ -52,7 +52,7 @@ static int broadcast_groups_get(sd_netlink *nl) {
int r;
assert(nl);
- assert(nl->fd > 0);
+ assert(nl->fd >= 0);
r = getsockopt(nl->fd, SOL_NETLINK, NETLINK_LIST_MEMBERSHIPS, NULL, &len);
if (r < 0) {
diff --git a/src/libsystemd/sd-network/sd-network.c b/src/libsystemd/sd-network/sd-network.c
index efbceba83d..c1f5867ee4 100644
--- a/src/libsystemd/sd-network/sd-network.c
+++ b/src/libsystemd/sd-network/sd-network.c
@@ -99,17 +99,17 @@ _public_ int sd_network_get_domains(char ***ret) {
return network_get_strv("DOMAINS", ret);
}
-_public_ int sd_network_link_get_setup_state(int ifindex, char **state) {
+static int network_link_get_string(int ifindex, const char *field, char **ret) {
_cleanup_free_ char *s = NULL, *p = NULL;
int r;
assert_return(ifindex > 0, -EINVAL);
- assert_return(state, -EINVAL);
+ assert_return(ret, -EINVAL);
- if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0)
+ if (asprintf(&p, "/run/systemd/netif/links/%i", ifindex) < 0)
return -ENOMEM;
- r = parse_env_file(p, NEWLINE, "ADMIN_STATE", &s, NULL);
+ r = parse_env_file(p, NEWLINE, field, &s, NULL);
if (r == -ENOENT)
return -ENODATA;
if (r < 0)
@@ -117,82 +117,72 @@ _public_ int sd_network_link_get_setup_state(int ifindex, char **state) {
if (isempty(s))
return -ENODATA;
- *state = s;
+ *ret = s;
s = NULL;
return 0;
}
-_public_ int sd_network_link_get_network_file(int ifindex, char **filename) {
- _cleanup_free_ char *s = NULL, *p = NULL;
+static int network_link_get_strv(int ifindex, const char *key, char ***ret) {
+ _cleanup_free_ char *p = NULL, *s = NULL;
+ _cleanup_strv_free_ char **a = NULL;
int r;
assert_return(ifindex > 0, -EINVAL);
- assert_return(filename, -EINVAL);
+ assert_return(ret, -EINVAL);
if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0)
return -ENOMEM;
- r = parse_env_file(p, NEWLINE, "NETWORK_FILE", &s, NULL);
+ r = parse_env_file(p, NEWLINE, key, &s, NULL);
if (r == -ENOENT)
return -ENODATA;
if (r < 0)
return r;
- if (isempty(s))
- return -ENODATA;
-
- *filename = s;
- s = NULL;
+ if (isempty(s)) {
+ *ret = NULL;
+ return 0;
+ }
- return 0;
-}
+ a = strv_split(s, " ");
+ if (!a)
+ return -ENOMEM;
-_public_ int sd_network_link_get_operational_state(int ifindex, char **state) {
- _cleanup_free_ char *s = NULL, *p = NULL;
- int r;
+ strv_uniq(a);
+ r = strv_length(a);
- assert_return(ifindex > 0, -EINVAL);
- assert_return(state, -EINVAL);
+ *ret = a;
+ a = NULL;
- if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0)
- return -ENOMEM;
+ return r;
+}
- r = parse_env_file(p, NEWLINE, "OPER_STATE", &s, NULL);
- if (r == -ENOENT)
- return -ENODATA;
- if (r < 0)
- return r;
- if (isempty(s))
- return -ENODATA;
+_public_ int sd_network_link_get_setup_state(int ifindex, char **state) {
+ return network_link_get_string(ifindex, "ADMIN_STATE", state);
+}
- *state = s;
- s = NULL;
+_public_ int sd_network_link_get_network_file(int ifindex, char **filename) {
+ return network_link_get_string(ifindex, "NETWORK_FILE", filename);
+}
- return 0;
+_public_ int sd_network_link_get_operational_state(int ifindex, char **state) {
+ return network_link_get_string(ifindex, "OPER_STATE", state);
}
_public_ int sd_network_link_get_llmnr(int ifindex, char **llmnr) {
- _cleanup_free_ char *s = NULL, *p = NULL;
- int r;
-
- assert_return(ifindex > 0, -EINVAL);
- assert_return(llmnr, -EINVAL);
-
- if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0)
- return -ENOMEM;
+ return network_link_get_string(ifindex, "LLMNR", llmnr);
+}
- r = parse_env_file(p, NEWLINE, "LLMNR", &s, NULL);
- if (r == -ENOENT)
- return -ENODATA;
- if (r < 0)
- return r;
- if (isempty(s))
- return -ENODATA;
+_public_ int sd_network_link_get_mdns(int ifindex, char **mdns) {
+ return network_link_get_string(ifindex, "MDNS", mdns);
+}
- *llmnr = s;
- s = NULL;
+_public_ int sd_network_link_get_dnssec(int ifindex, char **dnssec) {
+ return network_link_get_string(ifindex, "DNSSEC", dnssec);
+}
- return 0;
+_public_ int sd_network_link_get_dnssec_negative_trust_anchors(int ifindex, char ***nta) {
+ return network_link_get_strv(ifindex, "DNSSEC_NTA", nta);
}
_public_ int sd_network_link_get_lldp(int ifindex, char **lldp) {
@@ -221,85 +211,32 @@ _public_ int sd_network_link_get_lldp(int ifindex, char **lldp) {
}
int sd_network_link_get_timezone(int ifindex, char **ret) {
- _cleanup_free_ char *s = NULL, *p = NULL;
- int r;
-
- assert_return(ifindex > 0, -EINVAL);
- assert_return(ret, -EINVAL);
-
- if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0)
- return -ENOMEM;
-
- r = parse_env_file(p, NEWLINE, "TIMEZONE", &s, NULL);
- if (r == -ENOENT)
- return -ENODATA;
- if (r < 0)
- return r;
- if (isempty(s))
- return -ENODATA;
-
- *ret = s;
- s = NULL;
- return 0;
-}
-
-static int network_get_link_strv(const char *key, int ifindex, char ***ret) {
- _cleanup_free_ char *p = NULL, *s = NULL;
- _cleanup_strv_free_ char **a = NULL;
- int r;
-
- assert_return(ifindex > 0, -EINVAL);
- assert_return(ret, -EINVAL);
-
- if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0)
- return -ENOMEM;
-
- r = parse_env_file(p, NEWLINE, key, &s, NULL);
- if (r == -ENOENT)
- return -ENODATA;
- if (r < 0)
- return r;
- if (isempty(s)) {
- *ret = NULL;
- return 0;
- }
-
- a = strv_split(s, " ");
- if (!a)
- return -ENOMEM;
-
- strv_uniq(a);
- r = strv_length(a);
-
- *ret = a;
- a = NULL;
-
- return r;
+ return network_link_get_string(ifindex, "TIMEZONE", ret);
}
_public_ int sd_network_link_get_dns(int ifindex, char ***ret) {
- return network_get_link_strv("DNS", ifindex, ret);
+ return network_link_get_strv(ifindex, "DNS", ret);
}
_public_ int sd_network_link_get_ntp(int ifindex, char ***ret) {
- return network_get_link_strv("NTP", ifindex, ret);
+ return network_link_get_strv(ifindex, "NTP", ret);
}
_public_ int sd_network_link_get_domains(int ifindex, char ***ret) {
- return network_get_link_strv("DOMAINS", ifindex, ret);
+ return network_link_get_strv(ifindex, "DOMAINS", ret);
}
_public_ int sd_network_link_get_carrier_bound_to(int ifindex, char ***ret) {
- return network_get_link_strv("CARRIER_BOUND_TO", ifindex, ret);
+ return network_link_get_strv(ifindex, "CARRIER_BOUND_TO", ret);
}
_public_ int sd_network_link_get_carrier_bound_by(int ifindex, char ***ret) {
- return network_get_link_strv("CARRIER_BOUND_BY", ifindex, ret);
+ return network_link_get_strv(ifindex, "CARRIER_BOUND_BY", ret);
}
_public_ int sd_network_link_get_wildcard_domain(int ifindex) {
- int r;
_cleanup_free_ char *p = NULL, *s = NULL;
+ int r;
assert_return(ifindex > 0, -EINVAL);
diff --git a/src/libsystemd/sd-resolve/test-resolve.c b/src/libsystemd/sd-resolve/test-resolve.c
index e78a75c9ea..ce97e81ed6 100644
--- a/src/libsystemd/sd-resolve/test-resolve.c
+++ b/src/libsystemd/sd-resolve/test-resolve.c
@@ -101,11 +101,11 @@ int main(int argc, char *argv[]) {
if (r < 0)
log_error_errno(r, "sd_resolve_getnameinfo(): %m");
- /* Wait until the two queries are completed */
- while (sd_resolve_query_is_done(q1) == 0 ||
- sd_resolve_query_is_done(q2) == 0) {
-
+ /* Wait until all queries are completed */
+ for (;;) {
r = sd_resolve_wait(resolve, (uint64_t) -1);
+ if (r == 0)
+ break;
if (r < 0) {
log_error_errno(r, "sd_resolve_wait(): %m");
assert_not_reached("sd_resolve_wait() failed");
diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c
index c1643cf41a..4631f5fc90 100644
--- a/src/login/logind-dbus.c
+++ b/src/login/logind-dbus.c
@@ -1944,9 +1944,9 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_
action_multiple_sessions = "org.freedesktop.login1.halt-multiple-sessions";
action_ignore_inhibit = "org.freedesktop.login1.halt-ignore-inhibit";
} else if (streq(type, "poweroff")) {
- action = "org.freedesktop.login1.poweroff";
- action_multiple_sessions = "org.freedesktop.login1.poweroff-multiple-sessions";
- action_ignore_inhibit = "org.freedesktop.login1.poweroff-ignore-inhibit";
+ action = "org.freedesktop.login1.power-off";
+ action_multiple_sessions = "org.freedesktop.login1.power-off-multiple-sessions";
+ action_ignore_inhibit = "org.freedesktop.login1.power-off-ignore-inhibit";
} else
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unsupported shutdown type");
diff --git a/src/login/logind-seat.c b/src/login/logind-seat.c
index 1f4936cebe..9d111f737c 100644
--- a/src/login/logind-seat.c
+++ b/src/login/logind-seat.c
@@ -34,6 +34,7 @@
#include "logind-seat.h"
#include "mkdir.h"
#include "parse-util.h"
+#include "stdio-util.h"
#include "string-util.h"
#include "terminal-util.h"
#include "util.h"
@@ -181,7 +182,7 @@ static int vt_allocate(unsigned int vtnr) {
assert(vtnr >= 1);
- snprintf(p, sizeof(p), "/dev/tty%u", vtnr);
+ xsprintf(p, "/dev/tty%u", vtnr);
fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC);
if (fd < 0)
return -errno;
diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c
index 9f7c9952d3..03d32c6ed7 100644
--- a/src/machine/machine-dbus.c
+++ b/src/machine/machine-dbus.c
@@ -922,7 +922,7 @@ int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bu
First, we start by creating a private playground in /tmp,
that we can mount MS_SLAVE. (Which is necessary, since
- MS_MOUNT cannot be applied to mounts with MS_SHARED parent
+ MS_MOVE cannot be applied to mounts with MS_SHARED parent
mounts.) */
if (!mkdtemp(mount_slave))
diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c
index 0a01bd3e20..685bbafdf1 100644
--- a/src/machine/machinectl.c
+++ b/src/machine/machinectl.c
@@ -78,7 +78,6 @@ static unsigned arg_lines = 10;
static OutputMode arg_output = OUTPUT_SHORT;
static bool arg_force = false;
static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE;
-static const char* arg_dkr_index_url = NULL;
static const char* arg_format = NULL;
static const char *arg_uid = NULL;
static char **arg_setenv = NULL;
@@ -2166,78 +2165,6 @@ static int pull_raw(int argc, char *argv[], void *userdata) {
return transfer_image_common(bus, m);
}
-static int pull_dkr(int argc, char *argv[], void *userdata) {
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
- const char *local, *remote, *tag;
- sd_bus *bus = userdata;
- int r;
-
- if (arg_verify != IMPORT_VERIFY_NO) {
- log_error("Imports from DKR do not support image verification, please pass --verify=no.");
- return -EINVAL;
- }
-
- remote = argv[1];
- tag = strchr(remote, ':');
- if (tag) {
- remote = strndupa(remote, tag - remote);
- tag++;
- }
-
- if (!dkr_name_is_valid(remote)) {
- log_error("DKR name '%s' is invalid.", remote);
- return -EINVAL;
- }
- if (tag && !dkr_tag_is_valid(tag)) {
- log_error("DKR tag '%s' is invalid.", remote);
- return -EINVAL;
- }
-
- if (argc >= 3)
- local = argv[2];
- else {
- local = strchr(remote, '/');
- if (local)
- local++;
- else
- local = remote;
- }
-
- if (isempty(local) || streq(local, "-"))
- local = NULL;
-
- if (local) {
- if (!machine_name_is_valid(local)) {
- log_error("Local name %s is not a suitable machine name.", local);
- return -EINVAL;
- }
- }
-
- r = sd_bus_message_new_method_call(
- bus,
- &m,
- "org.freedesktop.import1",
- "/org/freedesktop/import1",
- "org.freedesktop.import1.Manager",
- "PullDkr");
- if (r < 0)
- return bus_log_create_error(r);
-
- r = sd_bus_message_append(
- m,
- "sssssb",
- arg_dkr_index_url,
- remote,
- tag,
- local,
- import_verify_to_string(arg_verify),
- arg_force);
- if (r < 0)
- return bus_log_create_error(r);
-
- return transfer_image_common(bus, m);
-}
-
typedef struct TransferInfo {
uint32_t id;
const char *type;
@@ -2452,9 +2379,7 @@ static int help(int argc, char *argv[], void *userdata) {
" json-pretty, json-sse, cat)\n"
" --verify=MODE Verification mode for downloaded images (no,\n"
" checksum, signature)\n"
- " --force Download image even if already exists\n"
- " --dkr-index-url=URL Specify the index URL to use for DKR image\n"
- " downloads\n\n"
+ " --force Download image even if already exists\n\n"
"Machine Commands:\n"
" list List running VMs and containers\n"
" status NAME... Show VM/container details\n"
@@ -2486,7 +2411,6 @@ static int help(int argc, char *argv[], void *userdata) {
"Image Transfer Commands:\n"
" pull-tar URL [NAME] Download a TAR container image\n"
" pull-raw URL [NAME] Download a RAW container or VM image\n"
- " pull-dkr REMOTE [NAME] Download a DKR container image\n"
" import-tar FILE [NAME] Import a local TAR container image\n"
" import-raw FILE [NAME] Import a local RAW container or VM image\n"
" export-tar NAME [FILE] Export a TAR container image locally\n"
@@ -2510,7 +2434,6 @@ static int parse_argv(int argc, char *argv[]) {
ARG_NO_ASK_PASSWORD,
ARG_VERIFY,
ARG_FORCE,
- ARG_DKR_INDEX_URL,
ARG_FORMAT,
ARG_UID,
ARG_SETENV,
@@ -2536,7 +2459,6 @@ static int parse_argv(int argc, char *argv[]) {
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{ "verify", required_argument, NULL, ARG_VERIFY },
{ "force", no_argument, NULL, ARG_FORCE },
- { "dkr-index-url", required_argument, NULL, ARG_DKR_INDEX_URL },
{ "format", required_argument, NULL, ARG_FORMAT },
{ "uid", required_argument, NULL, ARG_UID },
{ "setenv", required_argument, NULL, ARG_SETENV },
@@ -2650,15 +2572,6 @@ static int parse_argv(int argc, char *argv[]) {
arg_force = true;
break;
- case ARG_DKR_INDEX_URL:
- if (!http_url_is_valid(optarg)) {
- log_error("Index URL is invalid: %s", optarg);
- return -EINVAL;
- }
-
- arg_dkr_index_url = optarg;
- break;
-
case ARG_FORMAT:
if (!STR_IN_SET(optarg, "uncompressed", "xz", "gzip", "bzip2")) {
log_error("Unknown format: %s", optarg);
@@ -2725,7 +2638,6 @@ static int machinectl_main(int argc, char *argv[], sd_bus *bus) {
{ "export-raw", 2, 3, 0, export_raw },
{ "pull-tar", 2, 3, 0, pull_tar },
{ "pull-raw", 2, 3, 0, pull_raw },
- { "pull-dkr", 2, 3, 0, pull_dkr },
{ "list-transfers", VERB_ANY, 1, 0, list_transfers },
{ "cancel-transfer", 2, VERB_ANY, 0, cancel_transfer },
{ "set-limit", 2, 3, 0, set_limit },
diff --git a/src/network/networkctl.c b/src/network/networkctl.c
index 0234825adb..4a8fa4d8f3 100644
--- a/src/network/networkctl.c
+++ b/src/network/networkctl.c
@@ -40,6 +40,7 @@
#include "pager.h"
#include "parse-util.h"
#include "socket-util.h"
+#include "stdio-util.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
@@ -275,7 +276,8 @@ static int ieee_oui(sd_hwdb *hwdb, struct ether_addr *mac, char **ret) {
if (memcmp(mac, "\0\0\0", 3) == 0)
return -EINVAL;
- snprintf(modalias, sizeof(modalias), "OUI:" ETHER_ADDR_FORMAT_STR, ETHER_ADDR_FORMAT_VAL(*mac));
+ xsprintf(modalias, "OUI:" ETHER_ADDR_FORMAT_STR,
+ ETHER_ADDR_FORMAT_VAL(*mac));
r = sd_hwdb_get(hwdb, modalias, "ID_OUI_FROM_DATABASE", &description);
if (r < 0)
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index 9811526c6d..10fec5e75f 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -2495,7 +2495,7 @@ int link_ipv6ll_gained(Link *link, const struct in6_addr *address) {
link->ipv6ll_address = *address;
link_check_ready(link);
- if (!IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_PENDING, LINK_STATE_UNMANAGED, LINK_STATE_FAILED)) {
+ if (!IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_UNMANAGED, LINK_STATE_FAILED)) {
r = link_acquire_ipv6_conf(link);
if (r < 0) {
link_enter_failed(link);
@@ -2511,7 +2511,7 @@ static int link_carrier_gained(Link *link) {
assert(link);
- if (!IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_PENDING, LINK_STATE_UNMANAGED, LINK_STATE_FAILED)) {
+ if (!IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_UNMANAGED, LINK_STATE_FAILED)) {
r = link_acquire_conf(link);
if (r < 0) {
link_enter_failed(link);
@@ -2868,6 +2868,26 @@ int link_save(Link *link) {
fprintf(f, "LLMNR=%s\n",
resolve_support_to_string(link->network->llmnr));
+ fprintf(f, "MDNS=%s\n",
+ resolve_support_to_string(link->network->mdns));
+
+ if (link->network->dnssec_mode != _DNSSEC_MODE_INVALID)
+ fprintf(f, "DNSSEC=%s\n",
+ dnssec_mode_to_string(link->network->dnssec_mode));
+
+ if (!set_isempty(link->network->dnssec_negative_trust_anchors)) {
+ const char *n;
+
+ fputs("DNSSEC_NTA=", f);
+ space = false;
+ SET_FOREACH(n, link->network->dnssec_negative_trust_anchors, i) {
+ if (space)
+ fputc(' ', f);
+ fputs(n, f);
+ space = true;
+ }
+ fputc('\n', f);
+ }
fputs("ADDRESSES=", f);
space = false;
@@ -2881,7 +2901,6 @@ int link_save(Link *link) {
fprintf(f, "%s%s/%u", space ? " " : "", address_str, a->prefixlen);
space = true;
}
-
fputc('\n', f);
fputs("ROUTES=", f);
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index de2c66d153..2f2a36ccca 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -45,7 +45,10 @@ Network.Address, config_parse_address,
Network.Gateway, config_parse_gateway, 0, 0
Network.Domains, config_parse_domains, 0, offsetof(Network, domains)
Network.DNS, config_parse_strv, 0, offsetof(Network, dns)
-Network.LLMNR, config_parse_resolve, 0, offsetof(Network, llmnr)
+Network.LLMNR, config_parse_resolve_support, 0, offsetof(Network, llmnr)
+Network.MulticastDNS, config_parse_resolve_support, 0, offsetof(Network, mdns)
+Network.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Network, dnssec_mode)
+Network.DNSSECNegativeTrustAnchors, config_parse_dnssec_negative_trust_anchors, 0, offsetof(Network, dnssec_negative_trust_anchors)
Network.NTP, config_parse_strv, 0, offsetof(Network, ntp)
Network.IPForward, config_parse_address_family_boolean_with_kernel,0, offsetof(Network, ip_forward)
Network.IPMasquerade, config_parse_bool, 0, offsetof(Network, ip_masquerade)
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index 29723a852f..c11cb3dcb3 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -32,6 +32,7 @@
#include "networkd-network.h"
#include "networkd.h"
#include "parse-util.h"
+#include "set.h"
#include "stat-util.h"
#include "string-table.h"
#include "string-util.h"
@@ -121,6 +122,8 @@ static int network_load_one(Manager *manager, const char *filename) {
network->unicast_flood = true;
network->llmnr = RESOLVE_SUPPORT_YES;
+ network->mdns = RESOLVE_SUPPORT_NO;
+ network->dnssec_mode = _DNSSEC_MODE_INVALID;
network->link_local = ADDRESS_FAMILY_IPV6;
@@ -275,6 +278,8 @@ void network_free(Network *network) {
free(network->dhcp_server_dns);
free(network->dhcp_server_ntp);
+ set_free_free(network->dnssec_negative_trust_anchors);
+
free(network);
}
@@ -908,3 +913,55 @@ int config_parse_dhcp_server_ntp(
n->dhcp_server_ntp = m;
}
}
+
+int config_parse_dnssec_negative_trust_anchors(
+ 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) {
+
+ const char *p = rvalue;
+ Network *n = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ n->dnssec_negative_trust_anchors = set_free_free(n->dnssec_negative_trust_anchors);
+ return 0;
+ }
+
+ for (;;) {
+ _cleanup_free_ char *w = NULL;
+
+ r = extract_first_word(&p, &w, NULL, 0);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract negative trust anchor domain, ignoring: %s", rvalue);
+ break;
+ }
+ if (r == 0)
+ break;
+
+ r = dns_name_is_valid(w);
+ if (r <= 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "%s is not a valid domain name, ignoring.", w);
+ continue;
+ }
+
+ r = set_put(n->dnssec_negative_trust_anchors, w);
+ if (r < 0)
+ return log_oom();
+ if (r > 0)
+ w = NULL;
+ }
+
+ return 0;
+}
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index cb3a50d9ba..b07fa41abc 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -22,6 +22,7 @@
***/
#include "condition.h"
+#include "resolve-util.h"
typedef struct Network Network;
@@ -144,6 +145,9 @@ struct Network {
char **domains, **dns, **ntp, **bind_carrier;
ResolveSupport llmnr;
+ ResolveSupport mdns;
+ DnssecMode dnssec_mode;
+ Set *dnssec_negative_trust_anchors;
LIST_FIELDS(Network, networks);
};
@@ -170,6 +174,7 @@ int config_parse_hostname(const char *unit, const char *filename, unsigned line,
int config_parse_timezone(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_dhcp_server_dns(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_dhcp_server_ntp(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_dnssec_negative_trust_anchors(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);
/* Legacy IPv4LL support */
int config_parse_ipv4ll(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/network/networkd-util.c b/src/network/networkd-util.c
index 2545621a93..93135bb658 100644
--- a/src/network/networkd-util.c
+++ b/src/network/networkd-util.c
@@ -101,54 +101,3 @@ int config_parse_address_family_boolean_with_kernel(
return 0;
}
-
-static const char* const resolve_support_table[_RESOLVE_SUPPORT_MAX] = {
- [RESOLVE_SUPPORT_NO] = "no",
- [RESOLVE_SUPPORT_YES] = "yes",
- [RESOLVE_SUPPORT_RESOLVE] = "resolve",
-};
-
-DEFINE_STRING_TABLE_LOOKUP(resolve_support, ResolveSupport);
-
-int config_parse_resolve(
- 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) {
-
- ResolveSupport *resolve = data;
- int k;
-
- assert(filename);
- assert(lvalue);
- assert(rvalue);
- assert(resolve);
-
- /* Our enum shall be a superset of booleans, hence first try
- * to parse as boolean, and then as enum */
-
- k = parse_boolean(rvalue);
- if (k > 0)
- *resolve = RESOLVE_SUPPORT_YES;
- else if (k == 0)
- *resolve = RESOLVE_SUPPORT_NO;
- else {
- ResolveSupport s;
-
- s = resolve_support_from_string(rvalue);
- if (s < 0){
- log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse %s= option, ignoring: %s", lvalue, rvalue);
- return 0;
- }
-
- *resolve = s;
- }
-
- return 0;
-}
diff --git a/src/network/networkd-util.h b/src/network/networkd-util.h
index cc41aae85a..021ce4b128 100644
--- a/src/network/networkd-util.h
+++ b/src/network/networkd-util.h
@@ -33,20 +33,8 @@ typedef enum AddressFamilyBoolean {
_ADDRESS_FAMILY_BOOLEAN_INVALID = -1,
} AddressFamilyBoolean;
-typedef enum ResolveSupport {
- RESOLVE_SUPPORT_NO,
- RESOLVE_SUPPORT_YES,
- RESOLVE_SUPPORT_RESOLVE,
- _RESOLVE_SUPPORT_MAX,
- _RESOLVE_SUPPORT_INVALID = -1,
-} ResolveSupport;
-
-int config_parse_resolve(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_address_family_boolean(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_address_family_boolean_with_kernel(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);
-const char* resolve_support_to_string(ResolveSupport i) _const_;
-ResolveSupport resolve_support_from_string(const char *s) _pure_;
-
const char *address_family_boolean_to_string(AddressFamilyBoolean b) _const_;
AddressFamilyBoolean address_family_boolean_from_string(const char *s) _const_;
diff --git a/src/nspawn/nspawn-cgroup.c b/src/nspawn/nspawn-cgroup.c
index 270bcf010f..3c0e26ea5a 100644
--- a/src/nspawn/nspawn-cgroup.c
+++ b/src/nspawn/nspawn-cgroup.c
@@ -54,6 +54,7 @@ int chown_cgroup(pid_t pid, uid_t uid_shift) {
"tasks",
"notify_on_release",
"cgroup.procs",
+ "cgroup.events",
"cgroup.clone_children",
"cgroup.controllers",
"cgroup.subtree_control",
diff --git a/src/nspawn/nspawn-setuid.c b/src/nspawn/nspawn-setuid.c
index aa6a16309c..014a40b243 100644
--- a/src/nspawn/nspawn-setuid.c
+++ b/src/nspawn/nspawn-setuid.c
@@ -261,10 +261,10 @@ int change_uid_gid(const char *user, char **_home) {
return log_error_errno(errno, "Failed to set auxiliary groups: %m");
if (setresgid(gid, gid, gid) < 0)
- return log_error_errno(errno, "setregid() failed: %m");
+ return log_error_errno(errno, "setresgid() failed: %m");
if (setresuid(uid, uid, uid) < 0)
- return log_error_errno(errno, "setreuid() failed: %m");
+ return log_error_errno(errno, "setresuid() failed: %m");
if (_home) {
*_home = home;
diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c
index a4e13bd6aa..d619206dd6 100644
--- a/src/nspawn/nspawn.c
+++ b/src/nspawn/nspawn.c
@@ -1482,7 +1482,7 @@ static int setup_journal(const char *directory) {
}
static int drop_capabilities(void) {
- return capability_bounding_set_drop(~arg_retain, false);
+ return capability_bounding_set_drop(arg_retain, false);
}
static int reset_audit_loginuid(void) {
diff --git a/src/resolve-host/resolve-host.c b/src/resolve-host/resolve-host.c
index 0f154d9798..2cabfeaefa 100644
--- a/src/resolve-host/resolve-host.c
+++ b/src/resolve-host/resolve-host.c
@@ -33,16 +33,24 @@
#include "parse-util.h"
#include "resolved-def.h"
#include "resolved-dns-packet.h"
+#include "terminal-util.h"
#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC)
static int arg_family = AF_UNSPEC;
static int arg_ifindex = 0;
-static int arg_type = 0;
+static uint16_t arg_type = 0;
static uint16_t arg_class = 0;
static bool arg_legend = true;
static uint64_t arg_flags = 0;
-static bool arg_resolve_service = false;
+
+static enum {
+ MODE_RESOLVE_HOST,
+ MODE_RESOLVE_RECORD,
+ MODE_RESOLVE_SERVICE,
+ MODE_STATISTICS,
+ MODE_RESET_STATISTICS,
+} arg_mode = MODE_RESOLVE_HOST;
static void print_source(uint64_t flags, usec_t rtt) {
char rtt_str[FORMAT_TIMESTAMP_MAX];
@@ -320,8 +328,7 @@ static int parse_address(const char *s, int *family, union in_addr_union *addres
return 0;
}
-static int resolve_record(sd_bus *bus, const char *name) {
-
+static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_t type) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
char ifname[IF_NAMESIZE] = "";
@@ -335,7 +342,7 @@ static int resolve_record(sd_bus *bus, const char *name) {
if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname))
return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex);
- log_debug("Resolving %s %s %s (interface %s).", name, dns_class_to_string(arg_class), dns_type_to_string(arg_type), isempty(ifname) ? "*" : ifname);
+ log_debug("Resolving %s %s %s (interface %s).", name, dns_class_to_string(class), dns_type_to_string(type), isempty(ifname) ? "*" : ifname);
r = sd_bus_message_new_method_call(
bus,
@@ -347,8 +354,7 @@ static int resolve_record(sd_bus *bus, const char *name) {
if (r < 0)
return bus_log_create_error(r);
- assert((uint16_t) arg_type == arg_type);
- r = sd_bus_message_append(req, "isqqt", arg_ifindex, name, arg_class, arg_type, arg_flags);
+ r = sd_bus_message_append(req, "isqqt", arg_ifindex, name, class, type, arg_flags);
if (r < 0)
return bus_log_create_error(r);
@@ -369,7 +375,7 @@ static int resolve_record(sd_bus *bus, const char *name) {
while ((r = sd_bus_message_enter_container(reply, 'r', "iqqay")) > 0) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
- _cleanup_free_ char *s = NULL;
+ const char *s;
uint16_t c, t;
int ifindex;
const void *d;
@@ -399,17 +405,13 @@ static int resolve_record(sd_bus *bus, const char *name) {
if (r < 0)
return log_oom();
- r = dns_packet_read_rr(p, &rr, NULL);
- if (r < 0) {
- log_error("Failed to parse RR.");
- return r;
- }
+ r = dns_packet_read_rr(p, &rr, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse RR: %m");
- r = dns_resource_record_to_string(rr, &s);
- if (r < 0) {
- log_error("Failed to format RR.");
- return r;
- }
+ s = dns_resource_record_to_string(rr);
+ if (!s)
+ return log_oom();
ifname[0] = 0;
if (ifindex > 0 && !if_indextoname(ifindex, ifname))
@@ -439,6 +441,127 @@ static int resolve_record(sd_bus *bus, const char *name) {
return 0;
}
+static int resolve_rfc4501(sd_bus *bus, const char *name) {
+ uint16_t type = 0, class = 0;
+ const char *p, *q, *n;
+ int r;
+
+ assert(bus);
+ assert(name);
+ assert(startswith(name, "dns:"));
+
+ /* Parse RFC 4501 dns: URIs */
+
+ p = name + 4;
+
+ if (p[0] == '/') {
+ const char *e;
+
+ if (p[1] != '/')
+ goto invalid;
+
+ e = strchr(p + 2, '/');
+ if (!e)
+ goto invalid;
+
+ if (e != p + 2)
+ log_warning("DNS authority specification not supported; ignoring specified authority.");
+
+ p = e + 1;
+ }
+
+ q = strchr(p, '?');
+ if (q) {
+ n = strndupa(p, q - p);
+ q++;
+
+ for (;;) {
+ const char *f;
+
+ f = startswith_no_case(q, "class=");
+ if (f) {
+ _cleanup_free_ char *t = NULL;
+ const char *e;
+
+ if (class != 0) {
+ log_error("DNS class specified twice.");
+ return -EINVAL;
+ }
+
+ e = strchrnul(f, ';');
+ t = strndup(f, e - f);
+ if (!t)
+ return log_oom();
+
+ r = dns_class_from_string(t);
+ if (r < 0) {
+ log_error("Unknown DNS class %s.", t);
+ return -EINVAL;
+ }
+
+ class = r;
+
+ if (*e == ';') {
+ q = e + 1;
+ continue;
+ }
+
+ break;
+ }
+
+ f = startswith_no_case(q, "type=");
+ if (f) {
+ _cleanup_free_ char *t = NULL;
+ const char *e;
+
+ if (type != 0) {
+ log_error("DNS type specified twice.");
+ return -EINVAL;
+ }
+
+ e = strchrnul(f, ';');
+ t = strndup(f, e - f);
+ if (!t)
+ return log_oom();
+
+ r = dns_type_from_string(t);
+ if (r < 0) {
+ log_error("Unknown DNS type %s.", t);
+ return -EINVAL;
+ }
+
+ type = r;
+
+ if (*e == ';') {
+ q = e + 1;
+ continue;
+ }
+
+ break;
+ }
+
+ goto invalid;
+ }
+ } else
+ n = p;
+
+ if (type == 0)
+ type = arg_type;
+ if (type == 0)
+ type = DNS_TYPE_A;
+
+ if (class == 0)
+ class = arg_class;
+ if (class == 0)
+ class = DNS_CLASS_IN;
+
+ return resolve_record(bus, n, class, type);
+
+invalid:
+ log_error("Invalid DNS URI: %s", name);
+ return -EINVAL;
+}
+
static int resolve_service(sd_bus *bus, const char *name, const char *type, const char *domain) {
const char *canonical_name, *canonical_type, *canonical_domain;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL;
@@ -640,6 +763,125 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons
return 0;
}
+static int show_statistics(sd_bus *bus) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ uint64_t n_current_transactions, n_total_transactions,
+ cache_size, n_cache_hit, n_cache_miss,
+ n_dnssec_secure, n_dnssec_insecure, n_dnssec_bogus, n_dnssec_indeterminate;
+ int r;
+
+ assert(bus);
+
+ r = sd_bus_get_property(bus,
+ "org.freedesktop.resolve1",
+ "/org/freedesktop/resolve1",
+ "org.freedesktop.resolve1.Manager",
+ "TransactionStatistics",
+ &error,
+ &reply,
+ "(tt)");
+ if (r < 0)
+ return log_error_errno(r, "Failed to get transaction statistics: %s", bus_error_message(&error, r));
+
+ r = sd_bus_message_read(reply, "(tt)",
+ &n_current_transactions,
+ &n_total_transactions);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ printf("%sTransactions%s\n"
+ "Current Transactions: %" PRIu64 "\n"
+ " Total Transactions: %" PRIu64 "\n",
+ ansi_highlight(),
+ ansi_normal(),
+ n_current_transactions,
+ n_total_transactions);
+
+ reply = sd_bus_message_unref(reply);
+
+ r = sd_bus_get_property(bus,
+ "org.freedesktop.resolve1",
+ "/org/freedesktop/resolve1",
+ "org.freedesktop.resolve1.Manager",
+ "CacheStatistics",
+ &error,
+ &reply,
+ "(ttt)");
+ if (r < 0)
+ return log_error_errno(r, "Failed to get cache statistics: %s", bus_error_message(&error, r));
+
+ r = sd_bus_message_read(reply, "(ttt)",
+ &cache_size,
+ &n_cache_hit,
+ &n_cache_miss);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ printf("\n%sCache%s\n"
+ " Current Cache Size: %" PRIu64 "\n"
+ " Cache Hits: %" PRIu64 "\n"
+ " Cache Misses: %" PRIu64 "\n",
+ ansi_highlight(),
+ ansi_normal(),
+ cache_size,
+ n_cache_hit,
+ n_cache_miss);
+
+ reply = sd_bus_message_unref(reply);
+
+ r = sd_bus_get_property(bus,
+ "org.freedesktop.resolve1",
+ "/org/freedesktop/resolve1",
+ "org.freedesktop.resolve1.Manager",
+ "DNSSECStatistics",
+ &error,
+ &reply,
+ "(tttt)");
+ if (r < 0)
+ return log_error_errno(r, "Failed to get DNSSEC statistics: %s", bus_error_message(&error, r));
+
+ r = sd_bus_message_read(reply, "(tttt)",
+ &n_dnssec_secure,
+ &n_dnssec_insecure,
+ &n_dnssec_bogus,
+ &n_dnssec_indeterminate);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ printf("\n%sDNSSEC%s\n"
+ " Secure RRsets: %" PRIu64 "\n"
+ " Insecure RRsets: %" PRIu64 "\n"
+ " Bogus RRsets: %" PRIu64 "\n"
+ "Indeterminate RRsets: %" PRIu64 "\n",
+ ansi_highlight(),
+ ansi_normal(),
+ n_dnssec_secure,
+ n_dnssec_insecure,
+ n_dnssec_bogus,
+ n_dnssec_indeterminate);
+
+ return 0;
+}
+
+static int reset_statistics(sd_bus *bus) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ r = sd_bus_call_method(bus,
+ "org.freedesktop.resolve1",
+ "/org/freedesktop/resolve1",
+ "org.freedesktop.resolve1.Manager",
+ "ResetStatistics",
+ &error,
+ NULL,
+ NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to reset statistics: %s", bus_error_message(&error, r));
+
+ return 0;
+}
+
static void help_dns_types(void) {
int i;
const char *t;
@@ -684,6 +926,8 @@ static void help(void) {
" --cname=BOOL Do [not] follow CNAME redirects\n"
" --search=BOOL Do [not] use search domains\n"
" --legend=BOOL Do [not] print column headers\n"
+ " --statistics Show resolver statistics\n"
+ " --reset-statistics Reset resolver statistics\n"
, program_invocation_short_name, program_invocation_short_name);
}
@@ -696,20 +940,24 @@ static int parse_argv(int argc, char *argv[]) {
ARG_SERVICE_ADDRESS,
ARG_SERVICE_TXT,
ARG_SEARCH,
+ ARG_STATISTICS,
+ ARG_RESET_STATISTICS,
};
static const struct option options[] = {
- { "help", no_argument, NULL, 'h' },
- { "version", no_argument, NULL, ARG_VERSION },
- { "type", required_argument, NULL, 't' },
- { "class", required_argument, NULL, 'c' },
- { "legend", required_argument, NULL, ARG_LEGEND },
- { "protocol", required_argument, NULL, 'p' },
- { "cname", required_argument, NULL, ARG_CNAME },
- { "service", no_argument, NULL, ARG_SERVICE },
- { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS },
- { "service-txt", required_argument, NULL, ARG_SERVICE_TXT },
- { "search", required_argument, NULL, ARG_SEARCH },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "type", required_argument, NULL, 't' },
+ { "class", required_argument, NULL, 'c' },
+ { "legend", required_argument, NULL, ARG_LEGEND },
+ { "protocol", required_argument, NULL, 'p' },
+ { "cname", required_argument, NULL, ARG_CNAME },
+ { "service", no_argument, NULL, ARG_SERVICE },
+ { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS },
+ { "service-txt", required_argument, NULL, ARG_SERVICE_TXT },
+ { "search", required_argument, NULL, ARG_SEARCH },
+ { "statistics", no_argument, NULL, ARG_STATISTICS, },
+ { "reset-statistics", no_argument, NULL, ARG_RESET_STATISTICS },
{}
};
@@ -758,13 +1006,15 @@ static int parse_argv(int argc, char *argv[]) {
return 0;
}
- arg_type = dns_type_from_string(optarg);
- if (arg_type < 0) {
+ r = dns_type_from_string(optarg);
+ if (r < 0) {
log_error("Failed to parse RR record type %s", optarg);
- return arg_type;
+ return r;
}
- assert(arg_type > 0 && (uint16_t) arg_type == arg_type);
+ arg_type = (uint16_t) r;
+ assert((int) arg_type == r);
+ arg_mode = MODE_RESOLVE_RECORD;
break;
case 'c':
@@ -773,11 +1023,13 @@ static int parse_argv(int argc, char *argv[]) {
return 0;
}
- r = dns_class_from_string(optarg, &arg_class);
+ r = dns_class_from_string(optarg);
if (r < 0) {
log_error("Failed to parse RR record class %s", optarg);
return r;
}
+ arg_class = (uint16_t) r;
+ assert((int) arg_class == r);
break;
@@ -806,7 +1058,7 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_SERVICE:
- arg_resolve_service = true;
+ arg_mode = MODE_RESOLVE_SERVICE;
break;
case ARG_CNAME:
@@ -849,6 +1101,14 @@ static int parse_argv(int argc, char *argv[]) {
arg_flags &= ~SD_RESOLVED_NO_SEARCH;
break;
+ case ARG_STATISTICS:
+ arg_mode = MODE_STATISTICS;
+ break;
+
+ case ARG_RESET_STATISTICS:
+ arg_mode = MODE_RESET_STATISTICS;
+ break;
+
case '?':
return -EINVAL;
@@ -861,7 +1121,7 @@ static int parse_argv(int argc, char *argv[]) {
return -EINVAL;
}
- if (arg_type != 0 && arg_resolve_service) {
+ if (arg_type != 0 && arg_mode != MODE_RESOLVE_RECORD) {
log_error("--service and --type= may not be combined.");
return -EINVAL;
}
@@ -869,6 +1129,9 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_type != 0 && arg_class == 0)
arg_class = DNS_CLASS_IN;
+ if (arg_class != 0 && arg_type == 0)
+ arg_type = DNS_TYPE_A;
+
return 1 /* work to do */;
}
@@ -883,20 +1146,61 @@ int main(int argc, char **argv) {
if (r <= 0)
goto finish;
- if (optind >= argc) {
- log_error("No arguments passed");
- r = -EINVAL;
- goto finish;
- }
-
r = sd_bus_open_system(&bus);
if (r < 0) {
log_error_errno(r, "sd_bus_open_system: %m");
goto finish;
}
- if (arg_resolve_service) {
+ switch (arg_mode) {
+
+ case MODE_RESOLVE_HOST:
+ if (optind >= argc) {
+ log_error("No arguments passed");
+ r = -EINVAL;
+ goto finish;
+ }
+
+ while (argv[optind]) {
+ int family, ifindex, k;
+ union in_addr_union a;
+
+ if (startswith(argv[optind], "dns:"))
+ k = resolve_rfc4501(bus, argv[optind]);
+ else {
+ k = parse_address(argv[optind], &family, &a, &ifindex);
+ if (k >= 0)
+ k = resolve_address(bus, family, &a, ifindex);
+ else
+ k = resolve_host(bus, argv[optind]);
+ }
+
+ if (r == 0)
+ r = k;
+
+ optind++;
+ }
+ break;
+
+ case MODE_RESOLVE_RECORD:
+ if (optind >= argc) {
+ log_error("No arguments passed");
+ r = -EINVAL;
+ goto finish;
+ }
+
+ while (argv[optind]) {
+ int k;
+ k = resolve_record(bus, argv[optind], arg_class, arg_type);
+ if (r == 0)
+ r = k;
+
+ optind++;
+ }
+ break;
+
+ case MODE_RESOLVE_SERVICE:
if (argc < optind + 1) {
log_error("Domain specification required.");
r = -EINVAL;
@@ -914,27 +1218,27 @@ int main(int argc, char **argv) {
goto finish;
}
- goto finish;
- }
+ break;
- while (argv[optind]) {
- int family, ifindex, k;
- union in_addr_union a;
-
- if (arg_type != 0)
- k = resolve_record(bus, argv[optind]);
- else {
- k = parse_address(argv[optind], &family, &a, &ifindex);
- if (k >= 0)
- k = resolve_address(bus, family, &a, ifindex);
- else
- k = resolve_host(bus, argv[optind]);
+ case MODE_STATISTICS:
+ if (argc > optind) {
+ log_error("Too many arguments.");
+ r = -EINVAL;
+ goto finish;
}
- if (r == 0)
- r = k;
+ r = show_statistics(bus);
+ break;
+
+ case MODE_RESET_STATISTICS:
+ if (argc > optind) {
+ log_error("Too many arguments.");
+ r = -EINVAL;
+ goto finish;
+ }
- optind++;
+ r = reset_statistics(bus);
+ break;
}
finish:
diff --git a/src/resolve/RFCs b/src/resolve/RFCs
new file mode 100644
index 0000000000..33f4dd9cb6
--- /dev/null
+++ b/src/resolve/RFCs
@@ -0,0 +1,59 @@
+Y = Comprehensively Implemented, to the point appropriate for resolved
+D = Comprehensively Implemented, by a dependency of resolved
+! = Missing and something we might want to implement
+~ = Needs no explicit support or doesn't apply
+? = Is this relevant today?
+ = We are working on this
+
+Y https://tools.ietf.org/html/rfc1034 → DOMAIN NAMES - CONCEPTS AND FACILITIES
+Y https://tools.ietf.org/html/rfc1035 → DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION
+? https://tools.ietf.org/html/rfc1101 → DNS Encoding of Network Names and Other Types
+Y https://tools.ietf.org/html/rfc1123 → Requirements for Internet Hosts -- Application and Support
+~ https://tools.ietf.org/html/rfc1464 → Using the Domain Name System To Store Arbitrary String Attributes
+Y https://tools.ietf.org/html/rfc1536 → Common DNS Implementation Errors and Suggested Fixes
+Y https://tools.ietf.org/html/rfc1876 → A Means for Expressing Location Information in the Domain Name System
+Y https://tools.ietf.org/html/rfc2181 → Clarifications to the DNS Specification
+ https://tools.ietf.org/html/rfc2308 → Negative Caching of DNS Queries (DNS NCACHE)
+Y https://tools.ietf.org/html/rfc2782 → A DNS RR for specifying the location of services (DNS SRV)
+D https://tools.ietf.org/html/rfc3492 → Punycode: A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)
+Y https://tools.ietf.org/html/rfc3596 → DNS Extensions to Support IP Version 6
+Y https://tools.ietf.org/html/rfc3597 → Handling of Unknown DNS Resource Record (RR) Types
+ https://tools.ietf.org/html/rfc4033 → DNS Security Introduction and Requirements
+ https://tools.ietf.org/html/rfc4034 → Resource Records for the DNS Security Extensions
+ https://tools.ietf.org/html/rfc4035 → Protocol Modifications for the DNS Security Extensions
+! https://tools.ietf.org/html/rfc4183 → A Suggested Scheme for DNS Resolution of Networks and Gateways
+Y https://tools.ietf.org/html/rfc4255 → Using DNS to Securely Publish Secure Shell (SSH) Key Fingerprints
+Y https://tools.ietf.org/html/rfc4343 → Domain Name System (DNS) Case Insensitivity Clarification
+~ https://tools.ietf.org/html/rfc4470 → Minimally Covering NSEC Records and DNSSEC On-line Signing
+Y https://tools.ietf.org/html/rfc4501 → Domain Name System Uniform Resource Identifiers
+Y https://tools.ietf.org/html/rfc4509 → Use of SHA-256 in DNSSEC Delegation Signer (DS) Resource Records (RRs)
+~ https://tools.ietf.org/html/rfc4592 → The Role of Wildcards in the Domain Name System
+~ https://tools.ietf.org/html/rfc4697 → Observed DNS Resolution Misbehavior
+Y https://tools.ietf.org/html/rfc4795 → Link-Local Multicast Name Resolution (LLMNR)
+Y https://tools.ietf.org/html/rfc5011 → Automated Updates of DNS Security (DNSSEC) Trust Anchors
+ https://tools.ietf.org/html/rfc5155 → DNS Security (DNSSEC) Hashed Authenticated Denial of Existence
+Y https://tools.ietf.org/html/rfc5452 → Measures for Making DNS More Resilient against Forged Answers
+Y https://tools.ietf.org/html/rfc5702 → Use of SHA-2 Algorithms with RSA in DNSKEY and RRSIG Resource Records for DNSSEC
+Y https://tools.ietf.org/html/rfc5890 → Internationalized Domain Names for Applications (IDNA): Definitions and Document Framework
+Y https://tools.ietf.org/html/rfc5891 → Internationalized Domain Names in Applications (IDNA): Protocol
+Y https://tools.ietf.org/html/rfc5966 → DNS Transport over TCP - Implementation Requirements
+Y https://tools.ietf.org/html/rfc6303 → Locally Served DNS Zones
+ https://tools.ietf.org/html/rfc6604 → xNAME RCODE and Status Bits Clarification
+Y https://tools.ietf.org/html/rfc6605 → Elliptic Curve Digital Signature Algorithm (DSA) for DNSSEC
+ https://tools.ietf.org/html/rfc6672 → DNAME Redirection in the DNS
+! https://tools.ietf.org/html/rfc6731 → Improved Recursive DNS Server Selection for Multi-Interfaced Nodes
+Y https://tools.ietf.org/html/rfc6761 → Special-Use Domain Names
+ https://tools.ietf.org/html/rfc6762 → Multicast DNS
+ https://tools.ietf.org/html/rfc6763 → DNS-Based Service Discovery
+ https://tools.ietf.org/html/rfc6781 → DNSSEC Operational Practices, Version 2
+ https://tools.ietf.org/html/rfc6840 → Clarifications and Implementation Notes for DNS Security (DNSSEC)
+Y https://tools.ietf.org/html/rfc6891 → Extension Mechanisms for DNS (EDNS(0))
+Y https://tools.ietf.org/html/rfc6944 → Applicability Statement: DNS Security (DNSSEC) DNSKEY Algorithm Implementation Status
+Y https://tools.ietf.org/html/rfc6975 → Signaling Cryptographic Algorithm Understanding in DNS Security Extensions (DNSSEC)
+ https://tools.ietf.org/html/rfc7129 → Authenticated Denial of Existence in the DNS
+Y https://tools.ietf.org/html/rfc7646 → Definition and Use of DNSSEC Negative Trust Anchors
+~ https://tools.ietf.org/html/rfc7719 → DNS Terminology
+
+Also relevant:
+
+ https://www.iab.org/documents/correspondence-reports-documents/2013-2/iab-statement-dotless-domains-considered-harmful/
diff --git a/src/resolve/dns-type.c b/src/resolve/dns-type.c
index 63b4b36e88..fb8228048d 100644
--- a/src/resolve/dns-type.c
+++ b/src/resolve/dns-type.c
@@ -20,6 +20,7 @@
***/
#include "dns-type.h"
+#include "string-util.h"
typedef const struct {
uint16_t type;
@@ -44,7 +45,141 @@ int dns_type_from_string(const char *s) {
return sc->id;
}
-/* XXX: find an authoritative list of all pseudo types? */
-bool dns_type_is_pseudo(int n) {
- return IN_SET(n, DNS_TYPE_ANY, DNS_TYPE_AXFR, DNS_TYPE_IXFR, DNS_TYPE_OPT);
+bool dns_type_is_pseudo(uint16_t type) {
+
+ /* Checks whether the specified type is a "pseudo-type". What
+ * a "pseudo-type" precisely is, is defined only very weakly,
+ * but apparently entails all RR types that are not actually
+ * stored as RRs on the server and should hence also not be
+ * cached. We use this list primarily to validate NSEC type
+ * bitfields, and to verify what to cache. */
+
+ return IN_SET(type,
+ 0, /* A Pseudo RR type, according to RFC 2931 */
+ DNS_TYPE_ANY,
+ DNS_TYPE_AXFR,
+ DNS_TYPE_IXFR,
+ DNS_TYPE_OPT,
+ DNS_TYPE_TSIG,
+ DNS_TYPE_TKEY
+ );
+}
+
+bool dns_class_is_pseudo(uint16_t class) {
+ return class == DNS_TYPE_ANY;
+}
+
+bool dns_type_is_valid_query(uint16_t type) {
+
+ /* The types valid as questions in packets */
+
+ return !IN_SET(type,
+ 0,
+ DNS_TYPE_OPT,
+ DNS_TYPE_TSIG,
+ DNS_TYPE_TKEY,
+
+ /* RRSIG are technically valid as questions, but we refuse doing explicit queries for them, as
+ * they aren't really payload, but signatures for payload, and cannot be validated on their
+ * own. After all they are the signatures, and have no signatures of their own validating
+ * them. */
+ DNS_TYPE_RRSIG);
+}
+
+bool dns_type_is_valid_rr(uint16_t type) {
+
+ /* The types valid as RR in packets (but not necessarily
+ * stored on servers). */
+
+ return !IN_SET(type,
+ DNS_TYPE_ANY,
+ DNS_TYPE_AXFR,
+ DNS_TYPE_IXFR);
+}
+
+bool dns_class_is_valid_rr(uint16_t class) {
+ return class != DNS_CLASS_ANY;
+}
+
+bool dns_type_may_redirect(uint16_t type) {
+ /* The following record types should never be redirected using
+ * CNAME/DNAME RRs. See
+ * <https://tools.ietf.org/html/rfc4035#section-2.5>. */
+
+ if (dns_type_is_pseudo(type))
+ return false;
+
+ return !IN_SET(type,
+ DNS_TYPE_CNAME,
+ DNS_TYPE_DNAME,
+ DNS_TYPE_NSEC3,
+ DNS_TYPE_NSEC,
+ DNS_TYPE_RRSIG,
+ DNS_TYPE_NXT,
+ DNS_TYPE_SIG,
+ DNS_TYPE_KEY);
+}
+
+bool dns_type_is_dnssec(uint16_t type) {
+ return IN_SET(type,
+ DNS_TYPE_DS,
+ DNS_TYPE_DNSKEY,
+ DNS_TYPE_RRSIG,
+ DNS_TYPE_NSEC,
+ DNS_TYPE_NSEC3,
+ DNS_TYPE_NSEC3PARAM);
+}
+
+bool dns_type_is_obsolete(uint16_t type) {
+ return IN_SET(type,
+ /* Obsoleted by RFC 973 */
+ DNS_TYPE_MD,
+ DNS_TYPE_MF,
+ DNS_TYPE_MAILA,
+
+ /* Kinda obsoleted by RFC 2505 */
+ DNS_TYPE_MB,
+ DNS_TYPE_MG,
+ DNS_TYPE_MR,
+ DNS_TYPE_MINFO,
+ DNS_TYPE_MAILB,
+
+ /* RFC1127 kinda obsoleted this by recommending against its use */
+ DNS_TYPE_WKS,
+
+ /* Declared historical by RFC 6563 */
+ DNS_TYPE_A6,
+
+ /* Obsoleted by DNSSEC-bis */
+ DNS_TYPE_NXT,
+
+ /* RFC 1035 removed support for concepts that needed this from RFC 883 */
+ DNS_TYPE_NULL);
+}
+
+const char *dns_class_to_string(uint16_t class) {
+
+ switch (class) {
+
+ case DNS_CLASS_IN:
+ return "IN";
+
+ case DNS_CLASS_ANY:
+ return "ANY";
+ }
+
+ return NULL;
+}
+
+int dns_class_from_string(const char *s) {
+
+ if (!s)
+ return _DNS_CLASS_INVALID;
+
+ if (strcaseeq(s, "IN"))
+ return DNS_CLASS_IN;
+ else if (strcaseeq(s, "ANY"))
+ return DNS_CLASS_ANY;
+
+ return _DNS_CLASS_INVALID;
}
diff --git a/src/resolve/dns-type.h b/src/resolve/dns-type.h
index 950af36ee3..45080fd243 100644
--- a/src/resolve/dns-type.h
+++ b/src/resolve/dns-type.h
@@ -23,10 +23,6 @@
#include "macro.h"
-const char *dns_type_to_string(int type);
-int dns_type_from_string(const char *s);
-bool dns_type_is_pseudo(int n);
-
/* DNS record types, taken from
* http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml.
*/
@@ -119,3 +115,28 @@ enum {
assert_cc(DNS_TYPE_SSHFP == 44);
assert_cc(DNS_TYPE_TLSA == 52);
assert_cc(DNS_TYPE_ANY == 255);
+
+/* DNS record classes, see RFC 1035 */
+enum {
+ DNS_CLASS_IN = 0x01,
+ DNS_CLASS_ANY = 0xFF,
+
+ _DNS_CLASS_MAX,
+ _DNS_CLASS_INVALID = -1
+};
+
+bool dns_type_is_pseudo(uint16_t type);
+bool dns_type_is_valid_query(uint16_t type);
+bool dns_type_is_valid_rr(uint16_t type);
+bool dns_type_may_redirect(uint16_t type);
+bool dns_type_is_dnssec(uint16_t type);
+bool dns_type_is_obsolete(uint16_t type);
+
+bool dns_class_is_pseudo(uint16_t class);
+bool dns_class_is_valid_rr(uint16_t class);
+
+const char *dns_type_to_string(int type);
+int dns_type_from_string(const char *s);
+
+const char *dns_class_to_string(uint16_t type);
+int dns_class_from_string(const char *name);
diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c
index 0ceca56371..41f90dedfd 100644
--- a/src/resolve/resolved-bus.c
+++ b/src/resolve/resolved-bus.c
@@ -60,7 +60,17 @@ static int reply_query_state(DnsQuery *q) {
case DNS_TRANSACTION_ABORTED:
return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "Query aborted");
- case DNS_TRANSACTION_FAILURE: {
+ case DNS_TRANSACTION_DNSSEC_FAILED:
+ return sd_bus_reply_method_errorf(q->request, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed: %s",
+ dnssec_result_to_string(q->answer_dnssec_result));
+
+ case DNS_TRANSACTION_NO_TRUST_ANCHOR:
+ return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_TRUST_ANCHOR, "No suitable trust anchor known");
+
+ case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED:
+ return sd_bus_reply_method_errorf(q->request, BUS_ERROR_RR_TYPE_UNSUPPORTED, "Server does not support requested resource record type");
+
+ case DNS_TRANSACTION_RCODE_FAILURE: {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
if (q->answer_rcode == DNS_RCODE_NXDOMAIN)
@@ -84,6 +94,7 @@ static int reply_query_state(DnsQuery *q) {
case DNS_TRANSACTION_NULL:
case DNS_TRANSACTION_PENDING:
+ case DNS_TRANSACTION_VALIDATING:
case DNS_TRANSACTION_SUCCESS:
default:
assert_not_reached("Impossible state");
@@ -150,7 +161,7 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) {
}
if (r < 0)
goto finish;
- if (r > 0) /* This was a cname, and the query was restarted. */
+ if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */
return;
r = sd_bus_message_new_method_return(q->request, &reply);
@@ -305,7 +316,7 @@ static void bus_method_resolve_address_complete(DnsQuery *q) {
}
if (r < 0)
goto finish;
- if (r > 0) /* This was a cname, and the query was restarted. */
+ if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */
return;
r = sd_bus_message_new_method_return(q->request, &reply);
@@ -471,7 +482,7 @@ static void bus_method_resolve_record_complete(DnsQuery *q) {
}
if (r < 0)
goto finish;
- if (r > 0) /* Following a CNAME */
+ if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */
return;
r = sd_bus_message_new_method_return(q->request, &reply);
@@ -550,6 +561,11 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd
if (r == 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid name '%s'", name);
+ if (!dns_type_is_valid_query(type))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified resource record type %" PRIu16 " may not be used in a query.", type);
+ if (dns_type_is_obsolete(type))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Specified DNS resource record type %" PRIu16 " is obsolete.", type);
+
r = check_ifindex_flags(ifindex, &flags, 0, error);
if (r < 0)
return r;
@@ -886,7 +902,7 @@ static void resolve_service_hostname_complete(DnsQuery *q) {
}
r = dns_query_process_cname(q);
- if (r > 0) /* This was a cname, and the query was restarted. */
+ if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */
return;
/* This auxiliary lookup is finished or failed, let's see if all are finished now. */
@@ -962,10 +978,11 @@ static void bus_method_resolve_service_complete(DnsQuery *q) {
}
if (r < 0)
goto finish;
- if (r > 0) /* This was a cname, and the query was restarted. */
+ if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */
return;
if (q->answer) {
+ bool has_root_domain = false;
DnsResourceRecord *rr;
int ifindex;
@@ -979,6 +996,11 @@ static void bus_method_resolve_service_complete(DnsQuery *q) {
if (rr->key->type != DNS_TYPE_SRV)
continue;
+ if (dns_name_is_root(rr->srv.name)) {
+ has_root_domain = true;
+ continue;
+ }
+
if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
q->block_all_complete ++;
r = resolve_service_hostname(q, rr, ifindex);
@@ -990,6 +1012,18 @@ static void bus_method_resolve_service_complete(DnsQuery *q) {
found++;
}
+
+ if (has_root_domain && found == 0) {
+ /* If there's exactly one SRV RR and it uses
+ * the root domain as host name, then the
+ * service is explicitly not offered on the
+ * domain. Report this as a recognizable
+ * error. See RFC 2782, Section "Usage
+ * Rules". */
+ r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_SERVICE, "'%s' does not provide the requested service", dns_question_first_name(q->question));
+ goto finish;
+ }
+
}
if (found <= 0) {
@@ -1207,16 +1241,101 @@ static int bus_property_get_search_domains(
return sd_bus_message_close_container(reply);
}
+static int bus_property_get_transaction_statistics(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Manager *m = userdata;
+
+ assert(reply);
+ assert(m);
+
+ return sd_bus_message_append(reply, "(tt)",
+ (uint64_t) hashmap_size(m->dns_transactions),
+ (uint64_t) m->n_transactions_total);
+}
+
+static int bus_property_get_cache_statistics(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ uint64_t size = 0, hit = 0, miss = 0;
+ Manager *m = userdata;
+ DnsScope *s;
+
+ assert(reply);
+ assert(m);
+
+ LIST_FOREACH(scopes, s, m->dns_scopes) {
+ size += dns_cache_size(&s->cache);
+ hit += s->cache.n_hit;
+ miss += s->cache.n_miss;
+ }
+
+ return sd_bus_message_append(reply, "(ttt)", size, hit, miss);
+}
+
+static int bus_property_get_dnssec_statistics(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Manager *m = userdata;
+
+ assert(reply);
+ assert(m);
+
+ return sd_bus_message_append(reply, "(tttt)",
+ (uint64_t) m->n_dnssec_secure,
+ (uint64_t) m->n_dnssec_insecure,
+ (uint64_t) m->n_dnssec_bogus,
+ (uint64_t) m->n_dnssec_indeterminate);
+}
+
+static int bus_method_reset_statistics(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+ DnsScope *s;
+
+ assert(message);
+ assert(m);
+
+ LIST_FOREACH(scopes, s, m->dns_scopes)
+ s->cache.n_hit = s->cache.n_miss = 0;
+
+ m->n_transactions_total = 0;
+ m->n_dnssec_secure = m->n_dnssec_insecure = m->n_dnssec_bogus = m->n_dnssec_indeterminate = 0;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
static const sd_bus_vtable resolve_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("LLMNRHostname", "s", NULL, offsetof(Manager, llmnr_hostname), 0),
SD_BUS_PROPERTY("DNSServers", "a(iiay)", bus_property_get_dns_servers, 0, 0),
SD_BUS_PROPERTY("SearchDomains", "a(is)", bus_property_get_search_domains, 0, 0),
+ SD_BUS_PROPERTY("TransactionStatistics", "(tt)", bus_property_get_transaction_statistics, 0, 0),
+ SD_BUS_PROPERTY("CacheStatistics", "(ttt)", bus_property_get_cache_statistics, 0, 0),
+ SD_BUS_PROPERTY("DNSSECStatistics", "(tttt)", bus_property_get_dnssec_statistics, 0, 0),
SD_BUS_METHOD("ResolveHostname", "isit", "a(iiay)st", bus_method_resolve_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ResolveAddress", "iiayt", "a(is)t", bus_method_resolve_address, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ResolveRecord", "isqqt", "a(iqqay)t", bus_method_resolve_record, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ResolveService", "isssit", "a(qqqsa(iiay)s)aayssst", bus_method_resolve_service, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("ResetStatistics", NULL, NULL, bus_method_reset_statistics, 0),
SD_BUS_VTABLE_END,
};
@@ -1274,6 +1393,7 @@ int manager_connect_bus(Manager *m) {
if (r < 0)
return log_error_errno(r, "Failed to install bus reconnect time event: %m");
+ (void) sd_event_source_set_description(m->bus_retry_event_source, "bus-retry");
return 0;
}
diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c
index 1b2f3e336e..88df7534c4 100644
--- a/src/resolve/resolved-conf.c
+++ b/src/resolve/resolved-conf.c
@@ -200,75 +200,6 @@ int config_parse_search_domains(
return 0;
}
-int config_parse_support(
- 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) {
-
- Support support, *v = data;
- int r;
-
- assert(filename);
- assert(lvalue);
- assert(rvalue);
-
- support = support_from_string(rvalue);
- if (support < 0) {
- r = parse_boolean(rvalue);
- if (r < 0) {
- log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse support level '%s'. Ignoring.", rvalue);
- return 0;
- }
-
- support = r ? SUPPORT_YES : SUPPORT_NO;
- }
-
- *v = support;
- return 0;
-}
-
-int config_parse_dnssec(
- 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) {
-
- Manager *m = data;
- DnssecMode mode;
- int r;
-
- assert(filename);
- assert(lvalue);
- assert(rvalue);
-
- mode = dnssec_mode_from_string(rvalue);
- if (mode < 0) {
- r = parse_boolean(rvalue);
- if (r < 0) {
- log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse DNSSEC mode '%s'. Ignoring.", rvalue);
- return 0;
- }
-
- mode = r ? DNSSEC_YES : DNSSEC_NO;
- }
-
- m->unicast_scope->dnssec_mode = mode;
- return 0;
-}
-
int manager_parse_config_file(Manager *m) {
int r;
diff --git a/src/resolve/resolved-conf.h b/src/resolve/resolved-conf.h
index 668ea02bba..b4ef1b0378 100644
--- a/src/resolve/resolved-conf.h
+++ b/src/resolve/resolved-conf.h
@@ -35,5 +35,4 @@ const struct ConfigPerfItem* resolved_gperf_lookup(const char *key, unsigned len
int config_parse_dns_servers(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_search_domains(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_support(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_dnssec(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/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c
index 4db67f7278..b50558e280 100644
--- a/src/resolve/resolved-dns-answer.c
+++ b/src/resolve/resolved-dns-answer.c
@@ -22,6 +22,7 @@
#include "alloc-util.h"
#include "dns-domain.h"
#include "resolved-dns-answer.h"
+#include "resolved-dns-dnssec.h"
#include "string-util.h"
DnsAnswer *dns_answer_new(unsigned n) {
@@ -46,6 +47,18 @@ DnsAnswer *dns_answer_ref(DnsAnswer *a) {
return a;
}
+static void dns_answer_flush(DnsAnswer *a) {
+ DnsResourceRecord *rr;
+
+ if (!a)
+ return;
+
+ DNS_ANSWER_FOREACH(rr, a)
+ dns_resource_record_unref(rr);
+
+ a->n_rrs = 0;
+}
+
DnsAnswer *dns_answer_unref(DnsAnswer *a) {
if (!a)
return NULL;
@@ -53,11 +66,7 @@ DnsAnswer *dns_answer_unref(DnsAnswer *a) {
assert(a->n_ref > 0);
if (a->n_ref == 1) {
- unsigned i;
-
- for (i = 0; i < a->n_rrs; i++)
- dns_resource_record_unref(a->items[i].rr);
-
+ dns_answer_flush(a);
free(a);
} else
a->n_ref--;
@@ -65,7 +74,39 @@ DnsAnswer *dns_answer_unref(DnsAnswer *a) {
return NULL;
}
-int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex) {
+static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) {
+ assert(rr);
+
+ if (!a)
+ return -ENOSPC;
+
+ if (a->n_rrs >= a->n_allocated)
+ return -ENOSPC;
+
+ a->items[a->n_rrs++] = (DnsAnswerItem) {
+ .rr = dns_resource_record_ref(rr),
+ .ifindex = ifindex,
+ .flags = flags,
+ };
+
+ return 1;
+}
+
+static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) {
+ DnsResourceRecord *rr;
+ DnsAnswerFlags flags;
+ int ifindex, r;
+
+ DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, source) {
+ r = dns_answer_add_raw(a, rr, ifindex, flags);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) {
unsigned i;
int r;
@@ -73,6 +114,8 @@ int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex) {
if (!a)
return -ENOSPC;
+ if (a->n_ref > 1)
+ return -EBUSY;
for (i = 0; i < a->n_rrs; i++) {
if (a->items[i].ifindex != ifindex)
@@ -82,27 +125,66 @@ int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex) {
if (r < 0)
return r;
if (r > 0) {
- /* Entry already exists, keep the entry with
- * the higher RR, or the one with TTL 0 */
+ /* Don't mix contradicting TTLs (see below) */
+ if ((rr->ttl == 0) != (a->items[i].rr->ttl == 0))
+ return -EINVAL;
- if (rr->ttl == 0 || (rr->ttl > a->items[i].rr->ttl && a->items[i].rr->ttl != 0)) {
+ /* Entry already exists, keep the entry with
+ * the higher RR. */
+ if (rr->ttl > a->items[i].rr->ttl) {
dns_resource_record_ref(rr);
dns_resource_record_unref(a->items[i].rr);
a->items[i].rr = rr;
}
+ a->items[i].flags |= flags;
return 0;
}
+
+ r = dns_resource_key_equal(a->items[i].rr->key, rr->key);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ /* There's already an RR of the same RRset in
+ * place! Let's see if the TTLs more or less
+ * match. We don't really care if they match
+ * precisely, but we do care whether one is 0
+ * and the other is not. See RFC 2181, Section
+ * 5.2.*/
+
+ if ((rr->ttl == 0) != (a->items[i].rr->ttl == 0))
+ return -EINVAL;
+ }
}
- if (a->n_rrs >= a->n_allocated)
- return -ENOSPC;
+ return dns_answer_add_raw(a, rr, ifindex, flags);
+}
- a->items[a->n_rrs].rr = dns_resource_record_ref(rr);
- a->items[a->n_rrs].ifindex = ifindex;
- a->n_rrs++;
+static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) {
+ DnsResourceRecord *rr;
+ DnsAnswerFlags flags;
+ int ifindex, r;
- return 1;
+ DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, b) {
+ r = dns_answer_add(a, rr, ifindex, flags);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) {
+ int r;
+
+ assert(a);
+ assert(rr);
+
+ r = dns_answer_reserve_or_clone(a, 1);
+ if (r < 0)
+ return r;
+
+ return dns_answer_add(*a, rr, ifindex, flags);
}
int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl) {
@@ -128,101 +210,449 @@ int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl) {
soa->soa.expire = 1;
soa->soa.minimum = ttl;
- return dns_answer_add(a, soa, 0);
+ return dns_answer_add(a, soa, 0, DNS_ANSWER_AUTHENTICATED);
}
-int dns_answer_contains(DnsAnswer *a, DnsResourceKey *key) {
- unsigned i;
+int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags) {
+ DnsAnswerFlags flags = 0, i_flags;
+ DnsResourceRecord *i;
+ bool found = false;
int r;
assert(key);
- if (!a)
- return 0;
+ DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) {
+ r = dns_resource_key_match_rr(key, i, NULL);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
- for (i = 0; i < a->n_rrs; i++) {
- r = dns_resource_key_match_rr(key, a->items[i].rr, NULL);
+ if (!ret_flags)
+ return 1;
+
+ if (found)
+ flags &= i_flags;
+ else {
+ flags = i_flags;
+ found = true;
+ }
+ }
+
+ if (ret_flags)
+ *ret_flags = flags;
+
+ return found;
+}
+
+int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr, DnsAnswerFlags *ret_flags) {
+ DnsAnswerFlags flags = 0, i_flags;
+ DnsResourceRecord *i;
+ bool found = false;
+ int r;
+
+ assert(rr);
+
+ DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) {
+ r = dns_resource_record_equal(i, rr);
if (r < 0)
return r;
- if (r > 0)
+ if (r == 0)
+ continue;
+
+ if (!ret_flags)
return 1;
+
+ if (found)
+ flags &= i_flags;
+ else {
+ flags = i_flags;
+ found = true;
+ }
}
- return 0;
+ if (ret_flags)
+ *ret_flags = flags;
+
+ return found;
}
-int dns_answer_match_soa(DnsResourceKey *key, DnsResourceKey *soa) {
- if (soa->class != DNS_CLASS_IN)
- return 0;
+int dns_answer_contains_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags) {
+ DnsAnswerFlags flags = 0, i_flags;
+ DnsResourceRecord *i;
+ bool found = false;
+ int r;
+
+ assert(key);
+
+ DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) {
+ r = dns_resource_key_equal(i->key, key);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
- if (soa->type != DNS_TYPE_SOA)
+ if (!ret_flags)
+ return true;
+
+ if (found)
+ flags &= i_flags;
+ else {
+ flags = i_flags;
+ found = true;
+ }
+ }
+
+ if (ret_flags)
+ *ret_flags = flags;
+
+ return found;
+}
+
+int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a) {
+ DnsResourceRecord *i;
+
+ DNS_ANSWER_FOREACH(i, a) {
+ if (IN_SET(i->key->type, DNS_TYPE_NSEC, DNS_TYPE_NSEC3))
+ return true;
+ }
+
+ return false;
+}
+
+int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) {
+ DnsResourceRecord *rr, *soa = NULL;
+ DnsAnswerFlags rr_flags, soa_flags = 0;
+ int r;
+
+ assert(key);
+
+ /* For a SOA record we can never find a matching SOA record */
+ if (key->type == DNS_TYPE_SOA)
return 0;
- if (!dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(soa)))
+ DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) {
+ r = dns_resource_key_match_soa(key, rr->key);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+
+ if (soa) {
+ r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(soa->key));
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
+ }
+
+ soa = rr;
+ soa_flags = rr_flags;
+ }
+ }
+
+ if (!soa)
return 0;
+ if (ret)
+ *ret = soa;
+ if (flags)
+ *flags = soa_flags;
+
return 1;
}
-int dns_answer_find_soa(DnsAnswer *a, DnsResourceKey *key, DnsResourceRecord **ret) {
- unsigned i;
+int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) {
+ DnsResourceRecord *rr;
+ DnsAnswerFlags rr_flags;
+ int r;
assert(key);
+
+ /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */
+ if (!dns_type_may_redirect(key->type))
+ return 0;
+
+ DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) {
+ r = dns_resource_key_match_cname_or_dname(key, rr->key, NULL);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ if (ret)
+ *ret = rr;
+ if (flags)
+ *flags = rr_flags;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret) {
+ _cleanup_(dns_answer_unrefp) DnsAnswer *k = NULL;
+ int r;
+
assert(ret);
- if (!a)
+ if (dns_answer_size(a) <= 0) {
+ *ret = dns_answer_ref(b);
return 0;
+ }
- /* For a SOA record we can never find a matching SOA record */
- if (key->type == DNS_TYPE_SOA)
+ if (dns_answer_size(b) <= 0) {
+ *ret = dns_answer_ref(a);
return 0;
+ }
- for (i = 0; i < a->n_rrs; i++) {
+ k = dns_answer_new(a->n_rrs + b->n_rrs);
+ if (!k)
+ return -ENOMEM;
- if (dns_answer_match_soa(key, a->items[i].rr->key)) {
- *ret = a->items[i].rr;
- return 1;
- }
- }
+ r = dns_answer_add_raw_all(k, a);
+ if (r < 0)
+ return r;
+
+ r = dns_answer_add_all(k, b);
+ if (r < 0)
+ return r;
+
+ *ret = k;
+ k = NULL;
return 0;
}
-DnsAnswer *dns_answer_merge(DnsAnswer *a, DnsAnswer *b) {
- _cleanup_(dns_answer_unrefp) DnsAnswer *ret = NULL;
- DnsAnswer *k;
+int dns_answer_extend(DnsAnswer **a, DnsAnswer *b) {
+ DnsAnswer *merged;
+ int r;
+
+ assert(a);
+
+ r = dns_answer_merge(*a, b, &merged);
+ if (r < 0)
+ return r;
+
+ dns_answer_unref(*a);
+ *a = merged;
+
+ return 0;
+}
+
+int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key) {
+ bool found = false, other = false;
+ DnsResourceRecord *rr;
unsigned i;
int r;
- if (a && (!b || b->n_rrs <= 0))
- return dns_answer_ref(a);
- if ((!a || a->n_rrs <= 0) && b)
- return dns_answer_ref(b);
+ assert(a);
+ assert(key);
- ret = dns_answer_new((a ? a->n_rrs : 0) + (b ? b->n_rrs : 0));
- if (!ret)
- return NULL;
+ /* Remove all entries matching the specified key from *a */
- if (a) {
- for (i = 0; i < a->n_rrs; i++) {
- r = dns_answer_add(ret, a->items[i].rr, a->items[i].ifindex);
+ DNS_ANSWER_FOREACH(rr, *a) {
+ r = dns_resource_key_equal(rr->key, key);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ found = true;
+ else
+ other = true;
+
+ if (found && other)
+ break;
+ }
+
+ if (!found)
+ return 0;
+
+ if (!other) {
+ *a = dns_answer_unref(*a); /* Return NULL for the empty answer */
+ return 1;
+ }
+
+ if ((*a)->n_ref > 1) {
+ _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL;
+ DnsAnswerFlags flags;
+ int ifindex;
+
+ copy = dns_answer_new((*a)->n_rrs);
+ if (!copy)
+ return -ENOMEM;
+
+ DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) {
+ r = dns_resource_key_equal(rr->key, key);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
+
+ r = dns_answer_add_raw(copy, rr, ifindex, flags);
if (r < 0)
- return NULL;
+ return r;
}
+
+ dns_answer_unref(*a);
+ *a = copy;
+ copy = NULL;
+
+ return 1;
+ }
+
+ /* Only a single reference, edit in-place */
+
+ i = 0;
+ for (;;) {
+ if (i >= (*a)->n_rrs)
+ break;
+
+ r = dns_resource_key_equal((*a)->items[i].rr->key, key);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ /* Kill this entry */
+
+ dns_resource_record_unref((*a)->items[i].rr);
+ memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1));
+ (*a)->n_rrs --;
+ continue;
+
+ } else
+ /* Keep this entry */
+ i++;
+ }
+
+ return 1;
+}
+
+int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rm) {
+ bool found = false, other = false;
+ DnsResourceRecord *rr;
+ unsigned i;
+ int r;
+
+ assert(a);
+ assert(rm);
+
+ /* Remove all entries matching the specified RR from *a */
+
+ DNS_ANSWER_FOREACH(rr, *a) {
+ r = dns_resource_record_equal(rr, rm);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ found = true;
+ else
+ other = true;
+
+ if (found && other)
+ break;
}
- if (b) {
- for (i = 0; i < b->n_rrs; i++) {
- r = dns_answer_add(ret, b->items[i].rr, b->items[i].ifindex);
+ if (!found)
+ return 0;
+
+ if (!other) {
+ *a = dns_answer_unref(*a); /* Return NULL for the empty answer */
+ return 1;
+ }
+
+ if ((*a)->n_ref > 1) {
+ _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL;
+ DnsAnswerFlags flags;
+ int ifindex;
+
+ copy = dns_answer_new((*a)->n_rrs);
+ if (!copy)
+ return -ENOMEM;
+
+ DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) {
+ r = dns_resource_record_equal(rr, rm);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
+
+ r = dns_answer_add_raw(copy, rr, ifindex, flags);
if (r < 0)
- return NULL;
+ return r;
}
+
+ dns_answer_unref(*a);
+ *a = copy;
+ copy = NULL;
+
+ return 1;
+ }
+
+ /* Only a single reference, edit in-place */
+
+ i = 0;
+ for (;;) {
+ if (i >= (*a)->n_rrs)
+ break;
+
+ r = dns_resource_record_equal((*a)->items[i].rr, rm);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ /* Kill this entry */
+
+ dns_resource_record_unref((*a)->items[i].rr);
+ memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1));
+ (*a)->n_rrs --;
+ continue;
+
+ } else
+ /* Keep this entry */
+ i++;
+ }
+
+ return 1;
+}
+
+int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags) {
+ DnsResourceRecord *rr_source;
+ int ifindex_source, r;
+ DnsAnswerFlags flags_source;
+
+ assert(a);
+ assert(key);
+
+ /* Copy all RRs matching the specified key from source into *a */
+
+ DNS_ANSWER_FOREACH_FULL(rr_source, ifindex_source, flags_source, source) {
+
+ r = dns_resource_key_equal(rr_source->key, key);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ /* Make space for at least one entry */
+ r = dns_answer_reserve_or_clone(a, 1);
+ if (r < 0)
+ return r;
+
+ r = dns_answer_add(*a, rr_source, ifindex_source, flags_source|or_flags);
+ if (r < 0)
+ return r;
}
- k = ret;
- ret = NULL;
+ return 0;
+}
+
+int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags) {
+ int r;
+
+ assert(to);
+ assert(from);
+ assert(key);
+
+ r = dns_answer_copy_by_key(to, *from, key, or_flags);
+ if (r < 0)
+ return r;
- return k;
+ return dns_answer_remove_by_key(from, key);
}
void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local) {
@@ -261,6 +691,8 @@ void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local) {
int dns_answer_reserve(DnsAnswer **a, unsigned n_free) {
DnsAnswer *n;
+ assert(a);
+
if (n_free <= 0)
return 0;
@@ -275,6 +707,9 @@ int dns_answer_reserve(DnsAnswer **a, unsigned n_free) {
if ((*a)->n_allocated >= ns)
return 0;
+ /* Allocate more than we need */
+ ns *= 2;
+
n = realloc(*a, offsetof(DnsAnswer, items) + sizeof(DnsAnswerItem) * ns);
if (!n)
return -ENOMEM;
@@ -289,3 +724,73 @@ int dns_answer_reserve(DnsAnswer **a, unsigned n_free) {
*a = n;
return 0;
}
+
+int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free) {
+ _cleanup_(dns_answer_unrefp) DnsAnswer *n = NULL;
+ int r;
+
+ assert(a);
+
+ /* Tries to extend the DnsAnswer object. And if that's not
+ * possibly, since we are not the sole owner, then allocate a
+ * new, appropriately sized one. Either way, after this call
+ * the object will only have a single reference, and has room
+ * for at least the specified number of RRs. */
+
+ r = dns_answer_reserve(a, n_free);
+ if (r != -EBUSY)
+ return r;
+
+ assert(*a);
+
+ n = dns_answer_new(((*a)->n_rrs + n_free) * 2);
+ if (!n)
+ return -ENOMEM;
+
+ r = dns_answer_add_raw_all(n, *a);
+ if (r < 0)
+ return r;
+
+ dns_answer_unref(*a);
+ *a = n;
+ n = NULL;
+
+ return 0;
+}
+
+void dns_answer_dump(DnsAnswer *answer, FILE *f) {
+ DnsResourceRecord *rr;
+ DnsAnswerFlags flags;
+ int ifindex;
+
+ if (!f)
+ f = stdout;
+
+ DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) {
+ const char *t;
+
+ fputc('\t', f);
+
+ t = dns_resource_record_to_string(rr);
+ if (!t) {
+ log_oom();
+ continue;
+ }
+
+ fputs(t, f);
+
+ if (ifindex != 0 || flags & (DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE|DNS_ANSWER_SHARED_OWNER))
+ fputs("\t;", f);
+
+ if (ifindex != 0)
+ printf(" ifindex=%i", ifindex);
+ if (flags & DNS_ANSWER_AUTHENTICATED)
+ fputs(" authenticated", f);
+ if (flags & DNS_ANSWER_CACHEABLE)
+ fputs(" cachable", f);
+ if (flags & DNS_ANSWER_SHARED_OWNER)
+ fputs(" shared-owner", f);
+
+ fputc('\n', f);
+ }
+}
diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h
index 89c254b02e..715e487d94 100644
--- a/src/resolve/resolved-dns-answer.h
+++ b/src/resolve/resolved-dns-answer.h
@@ -30,11 +30,20 @@ typedef struct DnsAnswerItem DnsAnswerItem;
/* A simple array of resource records. We keep track of the
* originating ifindex for each RR where that makes sense, so that we
* can qualify A and AAAA RRs referring to a local link with the
- * right ifindex. */
+ * right ifindex.
+ *
+ * Note that we usually encode the the empty DnsAnswer object as a simple NULL. */
+
+typedef enum DnsAnswerFlags {
+ DNS_ANSWER_AUTHENTICATED = 1, /* Item has been authenticated */
+ DNS_ANSWER_CACHEABLE = 2, /* Item is subject to caching */
+ DNS_ANSWER_SHARED_OWNER = 4, /* For mDNS: RRset may be owner by multiple peers */
+} DnsAnswerFlags;
struct DnsAnswerItem {
DnsResourceRecord *rr;
int ifindex;
+ DnsAnswerFlags flags;
};
struct DnsAnswer {
@@ -47,16 +56,37 @@ DnsAnswer *dns_answer_new(unsigned n);
DnsAnswer *dns_answer_ref(DnsAnswer *a);
DnsAnswer *dns_answer_unref(DnsAnswer *a);
-int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex);
+int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags);
+int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags);
int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl);
-int dns_answer_contains(DnsAnswer *a, DnsResourceKey *key);
-int dns_answer_match_soa(DnsResourceKey *key, DnsResourceKey *soa);
-int dns_answer_find_soa(DnsAnswer *a, DnsResourceKey *key, DnsResourceRecord **ret);
-DnsAnswer *dns_answer_merge(DnsAnswer *a, DnsAnswer *b);
+int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *combined_flags);
+int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr, DnsAnswerFlags *combined_flags);
+int dns_answer_contains_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *combined_flags);
+int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a);
+
+int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags);
+int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags);
+
+int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret);
+int dns_answer_extend(DnsAnswer **a, DnsAnswer *b);
+
void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local);
int dns_answer_reserve(DnsAnswer **a, unsigned n_free);
+int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free);
+
+int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key);
+int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rr);
+
+int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags);
+int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags);
+
+static inline unsigned dns_answer_size(DnsAnswer *a) {
+ return a ? a->n_rrs : 0;
+}
+
+void dns_answer_dump(DnsAnswer *answer, FILE *f);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref);
@@ -70,13 +100,43 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref);
#define DNS_ANSWER_FOREACH(kk, a) _DNS_ANSWER_FOREACH(UNIQ, kk, a)
-#define _DNS_ANSWER_FOREACH_IFINDEX(q, kk, ifindex, a) \
+#define _DNS_ANSWER_FOREACH_IFINDEX(q, kk, ifi, a) \
for (unsigned UNIQ_T(i, q) = ({ \
(kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \
- (ifindex) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \
+ (ifi) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \
0; \
}); \
(a) && (UNIQ_T(i, q) < (a)->n_rrs); \
- UNIQ_T(i, q)++, (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), (ifindex) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0))
+ UNIQ_T(i, q)++, \
+ (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \
+ (ifi) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0))
#define DNS_ANSWER_FOREACH_IFINDEX(kk, ifindex, a) _DNS_ANSWER_FOREACH_IFINDEX(UNIQ, kk, ifindex, a)
+
+#define _DNS_ANSWER_FOREACH_FLAGS(q, kk, fl, a) \
+ for (unsigned UNIQ_T(i, q) = ({ \
+ (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \
+ (fl) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].flags : 0; \
+ 0; \
+ }); \
+ (a) && (UNIQ_T(i, q) < (a)->n_rrs); \
+ UNIQ_T(i, q)++, \
+ (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \
+ (fl) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].flags : 0))
+
+#define DNS_ANSWER_FOREACH_FLAGS(kk, flags, a) _DNS_ANSWER_FOREACH_FLAGS(UNIQ, kk, flags, a)
+
+#define _DNS_ANSWER_FOREACH_FULL(q, kk, ifi, fl, a) \
+ for (unsigned UNIQ_T(i, q) = ({ \
+ (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \
+ (ifi) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \
+ (fl) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].flags : 0; \
+ 0; \
+ }); \
+ (a) && (UNIQ_T(i, q) < (a)->n_rrs); \
+ UNIQ_T(i, q)++, \
+ (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \
+ (ifi) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0), \
+ (fl) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].flags : 0))
+
+#define DNS_ANSWER_FOREACH_FULL(kk, ifindex, flags, a) _DNS_ANSWER_FOREACH_FULL(UNIQ, kk, ifindex, flags, a)
diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c
index 6124ff659c..301f383809 100644
--- a/src/resolve/resolved-dns-cache.c
+++ b/src/resolve/resolved-dns-cache.c
@@ -21,15 +21,17 @@
#include "alloc-util.h"
#include "dns-domain.h"
+#include "resolved-dns-answer.h"
#include "resolved-dns-cache.h"
#include "resolved-dns-packet.h"
#include "string-util.h"
-/* Never cache more than 1K entries */
-#define CACHE_MAX 1024
+/* Never cache more than 4K entries. RFC 1536, Section 5 suggests to
+ * leave DNS caches unbounded, but that's crazy. */
+#define CACHE_MAX 4096
-/* We never keep any item longer than 10min in our cache */
-#define CACHE_TTL_MAX_USEC (10 * USEC_PER_MINUTE)
+/* We never keep any item longer than 2h in our cache */
+#define CACHE_TTL_MAX_USEC (2 * USEC_PER_HOUR)
typedef enum DnsCacheItemType DnsCacheItemType;
typedef struct DnsCacheItem DnsCacheItem;
@@ -41,14 +43,18 @@ enum DnsCacheItemType {
};
struct DnsCacheItem {
+ DnsCacheItemType type;
DnsResourceKey *key;
DnsResourceRecord *rr;
+
usec_t until;
- DnsCacheItemType type;
- unsigned prioq_idx;
- bool authenticated;
+ bool authenticated:1;
+ bool shared_owner:1;
+
int owner_family;
union in_addr_union owner_address;
+
+ unsigned prioq_idx;
LIST_FIELDS(DnsCacheItem, by_key);
};
@@ -63,7 +69,7 @@ static void dns_cache_item_free(DnsCacheItem *i) {
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem*, dns_cache_item_free);
-static void dns_cache_item_remove_and_free(DnsCache *c, DnsCacheItem *i) {
+static void dns_cache_item_unlink_and_free(DnsCache *c, DnsCacheItem *i) {
DnsCacheItem *first;
assert(c);
@@ -84,34 +90,55 @@ static void dns_cache_item_remove_and_free(DnsCache *c, DnsCacheItem *i) {
dns_cache_item_free(i);
}
-void dns_cache_flush(DnsCache *c) {
- DnsCacheItem *i;
+static bool dns_cache_remove_by_rr(DnsCache *c, DnsResourceRecord *rr) {
+ DnsCacheItem *first, *i;
+ int r;
+
+ first = hashmap_get(c->by_key, rr->key);
+ LIST_FOREACH(by_key, i, first) {
+ r = dns_resource_record_equal(i->rr, rr);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ dns_cache_item_unlink_and_free(c, i);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool dns_cache_remove_by_key(DnsCache *c, DnsResourceKey *key) {
+ DnsCacheItem *first, *i, *n;
assert(c);
+ assert(key);
- while ((i = hashmap_first(c->by_key)))
- dns_cache_item_remove_and_free(c, i);
+ first = hashmap_remove(c->by_key, key);
+ if (!first)
+ return false;
- assert(hashmap_size(c->by_key) == 0);
- assert(prioq_size(c->by_expiry) == 0);
+ LIST_FOREACH_SAFE(by_key, i, n, first) {
+ prioq_remove(c->by_expiry, i, &i->prioq_idx);
+ dns_cache_item_free(i);
+ }
- c->by_key = hashmap_free(c->by_key);
- c->by_expiry = prioq_free(c->by_expiry);
+ return true;
}
-static bool dns_cache_remove(DnsCache *c, DnsResourceKey *key) {
- DnsCacheItem *i;
- bool exist = false;
+void dns_cache_flush(DnsCache *c) {
+ DnsResourceKey *key;
assert(c);
- assert(key);
- while ((i = hashmap_get(c->by_key, key))) {
- dns_cache_item_remove_and_free(c, i);
- exist = true;
- }
+ while ((key = hashmap_first_key(c->by_key)))
+ dns_cache_remove_by_key(c, key);
- return exist;
+ assert(hashmap_size(c->by_key) == 0);
+ assert(prioq_size(c->by_expiry) == 0);
+
+ c->by_key = hashmap_free(c->by_key);
+ c->by_expiry = prioq_free(c->by_expiry);
}
static void dns_cache_make_space(DnsCache *c, unsigned add) {
@@ -141,7 +168,7 @@ static void dns_cache_make_space(DnsCache *c, unsigned add) {
/* Take an extra reference to the key so that it
* doesn't go away in the middle of the remove call */
key = dns_resource_key_ref(i->key);
- dns_cache_remove(c, key);
+ dns_cache_remove_by_key(c, key);
}
}
@@ -153,7 +180,6 @@ void dns_cache_prune(DnsCache *c) {
/* Remove all entries that are past their TTL */
for (;;) {
- _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
DnsCacheItem *i;
i = prioq_peek(c->by_expiry);
@@ -166,10 +192,19 @@ void dns_cache_prune(DnsCache *c) {
if (i->until > t)
break;
- /* Take an extra reference to the key so that it
- * doesn't go away in the middle of the remove call */
- key = dns_resource_key_ref(i->key);
- dns_cache_remove(c, key);
+ /* Depending whether this is an mDNS shared entry
+ * either remove only this one RR or the whole
+ * RRset */
+ if (i->shared_owner)
+ dns_cache_item_unlink_and_free(c, i);
+ else {
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
+
+ /* Take an extra reference to the key so that it
+ * doesn't go away in the middle of the remove call */
+ key = dns_resource_key_ref(i->key);
+ dns_cache_remove_by_key(c, key);
+ }
}
}
@@ -238,10 +273,56 @@ static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) {
return NULL;
}
-static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsResourceRecord *rr, bool authenticated, usec_t timestamp) {
+static usec_t calculate_until(DnsResourceRecord *rr, uint32_t nsec_ttl, usec_t timestamp, bool use_soa_minimum) {
+ uint32_t ttl;
+ usec_t u;
+
+ assert(rr);
+
+ ttl = MIN(rr->ttl, nsec_ttl);
+ if (rr->key->type == DNS_TYPE_SOA && use_soa_minimum) {
+ /* If this is a SOA RR, and it is requested, clamp to
+ * the SOA's minimum field. This is used when we do
+ * negative caching, to determine the TTL for the
+ * negative caching entry. See RFC 2308, Section
+ * 5. */
+
+ if (ttl > rr->soa.minimum)
+ ttl = rr->soa.minimum;
+ }
+
+ u = ttl * USEC_PER_SEC;
+ if (u > CACHE_TTL_MAX_USEC)
+ u = CACHE_TTL_MAX_USEC;
+
+ if (rr->expiry != USEC_INFINITY) {
+ usec_t left;
+
+ /* Make use of the DNSSEC RRSIG expiry info, if we
+ * have it */
+
+ left = LESS_BY(rr->expiry, now(CLOCK_REALTIME));
+ if (u > left)
+ u = left;
+ }
+
+ return timestamp + u;
+}
+
+static void dns_cache_item_update_positive(
+ DnsCache *c,
+ DnsCacheItem *i,
+ DnsResourceRecord *rr,
+ bool authenticated,
+ bool shared_owner,
+ usec_t timestamp,
+ int owner_family,
+ const union in_addr_union *owner_address) {
+
assert(c);
assert(i);
assert(rr);
+ assert(owner_address);
i->type = DNS_CACHE_POSITIVE;
@@ -258,8 +339,12 @@ static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsReso
dns_resource_key_unref(i->key);
i->key = dns_resource_key_ref(rr->key);
+ i->until = calculate_until(rr, (uint32_t) -1, timestamp, false);
i->authenticated = authenticated;
- i->until = timestamp + MIN(rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
+ i->shared_owner = shared_owner;
+
+ i->owner_family = owner_family;
+ i->owner_address = *owner_address;
prioq_reshuffle(c->by_expiry, i, &i->prioq_idx);
}
@@ -268,6 +353,7 @@ static int dns_cache_put_positive(
DnsCache *c,
DnsResourceRecord *rr,
bool authenticated,
+ bool shared_owner,
usec_t timestamp,
int owner_family,
const union in_addr_union *owner_address) {
@@ -275,35 +361,48 @@ static int dns_cache_put_positive(
_cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL;
_cleanup_free_ char *key_str = NULL;
DnsCacheItem *existing;
- int r;
+ int r, k;
assert(c);
assert(rr);
assert(owner_address);
- /* New TTL is 0? Delete the entry... */
+ /* Never cache pseudo RRs */
+ if (dns_class_is_pseudo(rr->key->class))
+ return 0;
+ if (dns_type_is_pseudo(rr->key->type))
+ return 0;
+
+ /* New TTL is 0? Delete this specific entry... */
if (rr->ttl <= 0) {
- r = dns_resource_key_to_string(rr->key, &key_str);
- if (r < 0)
- return r;
+ k = dns_cache_remove_by_rr(c, rr);
- if (dns_cache_remove(c, rr->key))
- log_debug("Removed zero TTL entry from cache: %s", key_str);
- else
- log_debug("Not caching zero TTL cache entry: %s", key_str);
+ if (log_get_max_level() >= LOG_DEBUG) {
+ r = dns_resource_key_to_string(rr->key, &key_str);
+ if (r < 0)
+ return r;
- return 0;
- }
+ if (k > 0)
+ log_debug("Removed zero TTL entry from cache: %s", key_str);
+ else
+ log_debug("Not caching zero TTL cache entry: %s", key_str);
+ }
- if (rr->key->class == DNS_CLASS_ANY)
- return 0;
- if (rr->key->type == DNS_TYPE_ANY)
return 0;
+ }
- /* Entry exists already? Update TTL and timestamp */
+ /* Entry exists already? Update TTL, timestamp and owner*/
existing = dns_cache_get(c, rr);
if (existing) {
- dns_cache_item_update_positive(c, existing, rr, authenticated, timestamp);
+ dns_cache_item_update_positive(
+ c,
+ existing,
+ rr,
+ authenticated,
+ shared_owner,
+ timestamp,
+ owner_family,
+ owner_address);
return 0;
}
@@ -321,21 +420,24 @@ static int dns_cache_put_positive(
i->type = DNS_CACHE_POSITIVE;
i->key = dns_resource_key_ref(rr->key);
i->rr = dns_resource_record_ref(rr);
- i->until = timestamp + MIN(i->rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
- i->prioq_idx = PRIOQ_IDX_NULL;
+ i->until = calculate_until(rr, (uint32_t) -1, timestamp, false);
+ i->authenticated = authenticated;
+ i->shared_owner = shared_owner;
i->owner_family = owner_family;
i->owner_address = *owner_address;
- i->authenticated = authenticated;
+ i->prioq_idx = PRIOQ_IDX_NULL;
r = dns_cache_link_item(c, i);
if (r < 0)
return r;
- r = dns_resource_key_to_string(i->key, &key_str);
- if (r < 0)
- return r;
+ if (log_get_max_level() >= LOG_DEBUG) {
+ r = dns_resource_key_to_string(i->key, &key_str);
+ if (r < 0)
+ return r;
- log_debug("Added cache entry for %s", key_str);
+ log_debug("Added positive cache entry for %s", key_str);
+ }
i = NULL;
return 0;
@@ -346,8 +448,9 @@ static int dns_cache_put_negative(
DnsResourceKey *key,
int rcode,
bool authenticated,
+ uint32_t nsec_ttl,
usec_t timestamp,
- uint32_t soa_ttl,
+ DnsResourceRecord *soa,
int owner_family,
const union in_addr_union *owner_address) {
@@ -357,20 +460,25 @@ static int dns_cache_put_negative(
assert(c);
assert(key);
+ assert(soa);
assert(owner_address);
- dns_cache_remove(c, key);
-
- if (key->class == DNS_CLASS_ANY)
+ /* Never cache pseudo RR keys. DNS_TYPE_ANY is particularly
+ * important to filter out as we use this as a pseudo-type for
+ * NXDOMAIN entries */
+ if (dns_class_is_pseudo(key->class))
return 0;
- if (key->type == DNS_TYPE_ANY)
+ if (dns_type_is_pseudo(key->type))
return 0;
- if (soa_ttl <= 0) {
- r = dns_resource_key_to_string(key, &key_str);
- if (r < 0)
- return r;
- log_debug("Not caching negative entry with zero SOA TTL: %s", key_str);
+ if (nsec_ttl <= 0 || soa->soa.minimum <= 0 || soa->ttl <= 0) {
+ if (log_get_max_level() >= LOG_DEBUG) {
+ r = dns_resource_key_to_string(key, &key_str);
+ if (r < 0)
+ return r;
+
+ log_debug("Not caching negative entry with zero SOA/NSEC/NSEC3 TTL: %s", key_str);
+ }
return 0;
}
@@ -389,65 +497,117 @@ static int dns_cache_put_negative(
return -ENOMEM;
i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN;
- i->key = dns_resource_key_ref(key);
- i->until = timestamp + MIN(soa_ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC);
- i->prioq_idx = PRIOQ_IDX_NULL;
+ i->until = calculate_until(soa, nsec_ttl, timestamp, true);
+ i->authenticated = authenticated;
i->owner_family = owner_family;
i->owner_address = *owner_address;
- i->authenticated = authenticated;
+ i->prioq_idx = PRIOQ_IDX_NULL;
+
+ if (i->type == DNS_CACHE_NXDOMAIN) {
+ /* NXDOMAIN entries should apply equally to all types, so we use ANY as
+ * a pseudo type for this purpose here. */
+ i->key = dns_resource_key_new(key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(key));
+ if (!i->key)
+ return -ENOMEM;
+
+ /* Make sure to remove any previous entry for this
+ * specific ANY key. (For non-ANY keys the cache data
+ * is already cleared by the caller.) Note that we
+ * don't bother removing positive or NODATA cache
+ * items in this case, because it would either be slow
+ * or require explicit indexing by name */
+ dns_cache_remove_by_key(c, key);
+ } else
+ i->key = dns_resource_key_ref(key);
r = dns_cache_link_item(c, i);
if (r < 0)
return r;
- r = dns_resource_key_to_string(i->key, &key_str);
- if (r < 0)
- return r;
+ if (log_get_max_level() >= LOG_DEBUG) {
+ r = dns_resource_key_to_string(i->key, &key_str);
+ if (r < 0)
+ return r;
- log_debug("Added %s cache entry for %s", i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", key_str);
+ log_debug("Added %s cache entry for %s", i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", key_str);
+ }
i = NULL;
return 0;
}
+static void dns_cache_remove_previous(
+ DnsCache *c,
+ DnsResourceKey *key,
+ DnsAnswer *answer) {
+
+ DnsResourceRecord *rr;
+ DnsAnswerFlags flags;
+
+ assert(c);
+
+ /* First, if we were passed a key (i.e. on LLMNR/DNS, but
+ * not on mDNS), delete all matching old RRs, so that we only
+ * keep complete by_key in place. */
+ if (key)
+ dns_cache_remove_by_key(c, key);
+
+ /* Second, flush all entries matching the answer, unless this
+ * is an RR that is explicitly marked to be "shared" between
+ * peers (i.e. mDNS RRs without the flush-cache bit set). */
+ DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
+ if ((flags & DNS_ANSWER_CACHEABLE) == 0)
+ continue;
+
+ if (flags & DNS_ANSWER_SHARED_OWNER)
+ continue;
+
+ dns_cache_remove_by_key(c, rr->key);
+ }
+}
+
int dns_cache_put(
DnsCache *c,
DnsResourceKey *key,
int rcode,
DnsAnswer *answer,
- unsigned max_rrs,
bool authenticated,
+ uint32_t nsec_ttl,
usec_t timestamp,
int owner_family,
const union in_addr_union *owner_address) {
- DnsResourceRecord *soa = NULL;
- unsigned cache_keys, i;
+ DnsResourceRecord *soa = NULL, *rr;
+ DnsAnswerFlags flags;
+ unsigned cache_keys;
int r;
assert(c);
+ assert(owner_address);
- if (key) {
- /* First, if we were passed a key, delete all matching old RRs,
- * so that we only keep complete by_key in place. */
- dns_cache_remove(c, key);
- }
+ dns_cache_remove_previous(c, key, answer);
- if (!answer)
- return 0;
+ if (dns_answer_size(answer) <= 0) {
+ if (log_get_max_level() >= LOG_DEBUG) {
+ _cleanup_free_ char *key_str = NULL;
+
+ r = dns_resource_key_to_string(key, &key_str);
+ if (r < 0)
+ return r;
+
+ log_debug("Not caching negative entry without a SOA record: %s", key_str);
+ }
- for (i = 0; i < answer->n_rrs; i++)
- dns_cache_remove(c, answer->items[i].rr->key);
+ return 0;
+ }
/* We only care for positive replies and NXDOMAINs, on all
* other replies we will simply flush the respective entries,
* and that's it */
-
if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN))
return 0;
- cache_keys = answer->n_rrs;
-
+ cache_keys = dns_answer_size(answer);
if (key)
cache_keys ++;
@@ -458,63 +618,63 @@ int dns_cache_put(
timestamp = now(clock_boottime_or_monotonic());
/* Second, add in positive entries for all contained RRs */
- for (i = 0; i < MIN(max_rrs, answer->n_rrs); i++) {
- DnsResourceRecord *rr = answer->items[i].rr;
-
- if (rr->key->cache_flush)
- dns_cache_remove(c, rr->key);
+ DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
+ if ((flags & DNS_ANSWER_CACHEABLE) == 0)
+ continue;
- r = dns_cache_put_positive(c, rr, authenticated, timestamp, owner_family, owner_address);
+ r = dns_cache_put_positive(
+ c,
+ rr,
+ flags & DNS_ANSWER_AUTHENTICATED,
+ flags & DNS_ANSWER_SHARED_OWNER,
+ timestamp,
+ owner_family, owner_address);
if (r < 0)
goto fail;
}
- if (!key)
+ if (!key) /* mDNS doesn't know negative caching, really */
return 0;
/* Third, add in negative entries if the key has no RR */
- r = dns_answer_contains(answer, key);
+ r = dns_answer_match_key(answer, key, NULL);
if (r < 0)
goto fail;
if (r > 0)
return 0;
- /* See https://tools.ietf.org/html/rfc2308, which
- * say that a matching SOA record in the packet
- * is used to to enable negative caching. */
+ /* But not if it has a matching CNAME/DNAME (the negative
+ * caching will be done on the canonical name, not on the
+ * alias) */
+ r = dns_answer_find_cname_or_dname(answer, key, NULL, NULL);
+ if (r < 0)
+ goto fail;
+ if (r > 0)
+ return 0;
- r = dns_answer_find_soa(answer, key, &soa);
+ /* See https://tools.ietf.org/html/rfc2308, which say that a
+ * matching SOA record in the packet is used to to enable
+ * negative caching. */
+ r = dns_answer_find_soa(answer, key, &soa, &flags);
if (r < 0)
goto fail;
if (r == 0)
return 0;
- /* Also, if the requested key is an alias, the negative response should
- be cached for each name in the redirect chain. Any CNAME record in
- the response is from the redirection chain, though only the final one
- is guaranteed to be included. This means that we cannot verify the
- chain and that we need to cache them all as it may be incomplete. */
- for (i = 0; i < answer->n_rrs; i++) {
- DnsResourceRecord *answer_rr = answer->items[i].rr;
-
- if (answer_rr->key->type == DNS_TYPE_CNAME) {
- _cleanup_(dns_resource_key_unrefp) DnsResourceKey *canonical_key = NULL;
-
- canonical_key = dns_resource_key_new_redirect(key, answer_rr);
- if (!canonical_key)
- goto fail;
-
- /* Let's not add negative cache entries for records outside the current zone. */
- if (!dns_answer_match_soa(canonical_key, soa->key))
- continue;
-
- r = dns_cache_put_negative(c, canonical_key, rcode, authenticated, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address);
- if (r < 0)
- goto fail;
- }
- }
+ /* Refuse using the SOA data if it is unsigned, but the key is
+ * signed */
+ if (authenticated && (flags & DNS_ANSWER_AUTHENTICATED) == 0)
+ return 0;
- r = dns_cache_put_negative(c, key, rcode, authenticated, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address);
+ r = dns_cache_put_negative(
+ c,
+ key,
+ rcode,
+ authenticated,
+ nsec_ttl,
+ timestamp,
+ soa,
+ owner_family, owner_address);
if (r < 0)
goto fail;
@@ -525,10 +685,14 @@ fail:
* added, just in case */
if (key)
- dns_cache_remove(c, key);
+ dns_cache_remove_by_key(c, key);
- for (i = 0; i < answer->n_rrs; i++)
- dns_cache_remove(c, answer->items[i].rr->key);
+ DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
+ if ((flags & DNS_ANSWER_CACHEABLE) == 0)
+ continue;
+
+ dns_cache_remove_by_key(c, rr->key);
+ }
return r;
}
@@ -545,36 +709,44 @@ static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, D
* much, after all this is just a cache */
i = hashmap_get(c->by_key, k);
- if (i || IN_SET(k->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME, DNS_TYPE_NSEC))
+ if (i)
return i;
n = DNS_RESOURCE_KEY_NAME(k);
- /* Check if we have an NSEC record instead for the name. */
- i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_NSEC, n));
- if (i)
+ /* Check if we have an NXDOMAIN cache item for the name, notice that we use
+ * the pseudo-type ANY for NXDOMAIN cache items. */
+ i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_ANY, n));
+ if (i && i->type == DNS_CACHE_NXDOMAIN)
return i;
- /* Check if we have a CNAME record instead */
- i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_CNAME, n));
- if (i)
- return i;
+ if (dns_type_may_redirect(k->type)) {
+ /* Check if we have a CNAME record instead */
+ i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_CNAME, n));
+ if (i)
+ return i;
- /* OK, let's look for cached DNAME records. */
- for (;;) {
- char label[DNS_LABEL_MAX];
+ /* OK, let's look for cached DNAME records. */
+ for (;;) {
+ if (isempty(n))
+ return NULL;
- if (isempty(n))
- return NULL;
+ i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_DNAME, n));
+ if (i)
+ return i;
+
+ /* Jump one label ahead */
+ r = dns_name_parent(&n);
+ if (r <= 0)
+ return NULL;
+ }
+ }
- i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_DNAME, n));
+ if (k->type != DNS_TYPE_NSEC) {
+ /* Check if we have an NSEC record instead for the name. */
+ i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_NSEC, n));
if (i)
return i;
-
- /* Jump one label ahead */
- r = dns_label_unescape(&n, label, sizeof(label));
- if (r <= 0)
- return NULL;
}
return NULL;
@@ -601,11 +773,15 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
/* If we have ANY lookups we don't use the cache, so
* that the caller refreshes via the network. */
- r = dns_resource_key_to_string(key, &key_str);
- if (r < 0)
- return r;
+ if (log_get_max_level() >= LOG_DEBUG) {
+ r = dns_resource_key_to_string(key, &key_str);
+ if (r < 0)
+ return r;
- log_debug("Ignoring cache for ANY lookup: %s", key_str);
+ log_debug("Ignoring cache for ANY lookup: %s", key_str);
+ }
+
+ c->n_miss++;
*ret = NULL;
*rcode = DNS_RCODE_SUCCESS;
@@ -616,11 +792,15 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
if (!first) {
/* If one question cannot be answered we need to refresh */
- r = dns_resource_key_to_string(key, &key_str);
- if (r < 0)
- return r;
+ if (log_get_max_level() >= LOG_DEBUG) {
+ r = dns_resource_key_to_string(key, &key_str);
+ if (r < 0)
+ return r;
- log_debug("Cache miss for %s", key_str);
+ log_debug("Cache miss for %s", key_str);
+ }
+
+ c->n_miss++;
*ret = NULL;
*rcode = DNS_RCODE_SUCCESS;
@@ -642,32 +822,48 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
have_non_authenticated = true;
}
- r = dns_resource_key_to_string(key, &key_str);
- if (r < 0)
- return r;
-
if (nsec && key->type != DNS_TYPE_NSEC) {
- log_debug("NSEC NODATA cache hit for %s", key_str);
+ if (log_get_max_level() >= LOG_DEBUG) {
+ r = dns_resource_key_to_string(key, &key_str);
+ if (r < 0)
+ return r;
+
+ log_debug("NSEC NODATA cache hit for %s", key_str);
+ }
/* We only found an NSEC record that matches our name.
- * If it says the type doesn't exit report
+ * If it says the type doesn't exist report
* NODATA. Otherwise report a cache miss. */
*ret = NULL;
*rcode = DNS_RCODE_SUCCESS;
*authenticated = nsec->authenticated;
- return !bitmap_isset(nsec->rr->nsec.types, key->type) &&
- !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_CNAME) &&
- !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_DNAME);
+ if (!bitmap_isset(nsec->rr->nsec.types, key->type) &&
+ !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_CNAME) &&
+ !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_DNAME)) {
+ c->n_hit++;
+ return 1;
+ }
+
+ c->n_miss++;
+ return 0;
}
- log_debug("%s cache hit for %s",
- n > 0 ? "Positive" :
- nxdomain ? "NXDOMAIN" : "NODATA",
- key_str);
+ if (log_get_max_level() >= LOG_DEBUG) {
+ r = dns_resource_key_to_string(key, &key_str);
+ if (r < 0)
+ return r;
+
+ log_debug("%s cache hit for %s",
+ n > 0 ? "Positive" :
+ nxdomain ? "NXDOMAIN" : "NODATA",
+ key_str);
+ }
if (n <= 0) {
+ c->n_hit++;
+
*ret = NULL;
*rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS;
*authenticated = have_authenticated && !have_non_authenticated;
@@ -682,11 +878,13 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
if (!j->rr)
continue;
- r = dns_answer_add(answer, j->rr, 0);
+ r = dns_answer_add(answer, j->rr, 0, j->authenticated ? DNS_ANSWER_AUTHENTICATED : 0);
if (r < 0)
return r;
}
+ c->n_hit++;
+
*ret = answer;
*rcode = DNS_RCODE_SUCCESS;
*authenticated = have_authenticated && !have_non_authenticated;
@@ -738,20 +936,36 @@ int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p) {
int r;
assert(cache);
+ assert(p);
HASHMAP_FOREACH(i, cache->by_key, iterator) {
DnsCacheItem *j;
LIST_FOREACH(by_key, j, i) {
- _cleanup_free_ char *t = NULL;
-
if (!j->rr)
continue;
- if (!dns_key_is_shared(j->rr->key))
+ if (!j->shared_owner)
continue;
r = dns_packet_append_rr(p, j->rr, NULL, NULL);
+ if (r == -EMSGSIZE && p->protocol == DNS_PROTOCOL_MDNS) {
+ /* For mDNS, if we're unable to stuff all known answers into the given packet,
+ * allocate a new one, push the RR into that one and link it to the current one.
+ */
+
+ DNS_PACKET_HEADER(p)->ancount = htobe16(ancount);
+ ancount = 0;
+
+ r = dns_packet_new_query(&p->more, p->protocol, 0, true);
+ if (r < 0)
+ return r;
+
+ /* continue with new packet */
+ p = p->more;
+ r = dns_packet_append_rr(p, j->rr, NULL, NULL);
+ }
+
if (r < 0)
return r;
@@ -779,13 +993,13 @@ void dns_cache_dump(DnsCache *cache, FILE *f) {
DnsCacheItem *j;
LIST_FOREACH(by_key, j, i) {
- _cleanup_free_ char *t = NULL;
fputc('\t', f);
if (j->rr) {
- r = dns_resource_record_to_string(j->rr, &t);
- if (r < 0) {
+ const char *t;
+ t = dns_resource_record_to_string(j->rr);
+ if (!t) {
log_oom();
continue;
}
@@ -793,13 +1007,14 @@ void dns_cache_dump(DnsCache *cache, FILE *f) {
fputs(t, f);
fputc('\n', f);
} else {
- r = dns_resource_key_to_string(j->key, &t);
+ _cleanup_free_ char *z = NULL;
+ r = dns_resource_key_to_string(j->key, &z);
if (r < 0) {
log_oom();
continue;
}
- fputs(t, f);
+ fputs(z, f);
fputs(" -- ", f);
fputs(j->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", f);
fputc('\n', f);
@@ -814,3 +1029,10 @@ bool dns_cache_is_empty(DnsCache *cache) {
return hashmap_isempty(cache->by_key);
}
+
+unsigned dns_cache_size(DnsCache *cache) {
+ if (!cache)
+ return 0;
+
+ return hashmap_size(cache->by_key);
+}
diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h
index 0f28bbe543..e61b285df4 100644
--- a/src/resolve/resolved-dns-cache.h
+++ b/src/resolve/resolved-dns-cache.h
@@ -29,6 +29,8 @@
typedef struct DnsCache {
Hashmap *by_key;
Prioq *by_expiry;
+ unsigned n_hit;
+ unsigned n_miss;
} DnsCache;
#include "resolved-dns-answer.h"
@@ -39,7 +41,7 @@ typedef struct DnsCache {
void dns_cache_flush(DnsCache *c);
void dns_cache_prune(DnsCache *c);
-int dns_cache_put(DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, unsigned max_rrs, bool authenticated, usec_t timestamp, int owner_family, const union in_addr_union *owner_address);
+int dns_cache_put(DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, bool authenticated, uint32_t nsec_ttl, usec_t timestamp, int owner_family, const union in_addr_union *owner_address);
int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **answer, bool *authenticated);
int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address);
@@ -47,4 +49,6 @@ int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_
void dns_cache_dump(DnsCache *cache, FILE *f);
bool dns_cache_is_empty(DnsCache *cache);
+unsigned dns_cache_size(DnsCache *cache);
+
int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p);
diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c
index 2d06775dca..43fcbe1460 100644
--- a/src/resolve/resolved-dns-dnssec.c
+++ b/src/resolve/resolved-dns-dnssec.c
@@ -23,6 +23,7 @@
#include "alloc-util.h"
#include "dns-domain.h"
+#include "hexdecoct.h"
#include "resolved-dns-dnssec.h"
#include "resolved-dns-packet.h"
#include "string-table.h"
@@ -34,17 +35,17 @@
*
* TODO:
*
- * - Iterative validation
- * - NSEC proof of non-existance
- * - NSEC3 proof of non-existance
- * - Make trust anchor store read additional DS+DNSKEY data from disk
- * - wildcard zones compatibility
+ * - wildcard zones compatibility (NSEC/NSEC3 wildcard check is missing)
* - multi-label zone compatibility
- * - DMSSEC cname/dname compatibility
- * - per-interface DNSSEC setting
- * - DSA support
- * - EC support?
+ * - cname/dname compatibility
+ * - nxdomain on qname
+ * - bus calls to override DNSEC setting per interface
+ * - log all DNSSEC downgrades
+ * - enable by default
*
+ * - RFC 4035, Section 5.3.4 (When receiving a positive wildcard reply, use NSEC to ensure it actually really applies)
+ * - RFC 6840, Section 4.1 (ensure we don't get fed a glue NSEC from the parent zone)
+ * - RFC 6840, Section 4.3 (check for CNAME on NSEC too)
* */
#define VERIFY_RRS_MAX 256
@@ -53,6 +54,9 @@
/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE)
+/* Maximum number of NSEC3 iterations we'll do. RFC5155 says 2500 shall be the maximum useful value */
+#define NSEC3_ITERATIONS_MAX 2500
+
/*
* The DNSSEC Chain of trust:
*
@@ -64,23 +68,22 @@
* Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
*/
-static bool dnssec_algorithm_supported(int algorithm) {
- return IN_SET(algorithm,
- DNSSEC_ALGORITHM_RSASHA1,
- DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1,
- DNSSEC_ALGORITHM_RSASHA256,
- DNSSEC_ALGORITHM_RSASHA512);
-}
+static void initialize_libgcrypt(void) {
+ const char *p;
+
+ if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P))
+ return;
-static bool dnssec_digest_supported(int digest) {
- return IN_SET(digest,
- DNSSEC_DIGEST_SHA1,
- DNSSEC_DIGEST_SHA256);
+ p = gcry_check_version("1.4.5");
+ assert(p);
+
+ gcry_control(GCRYCTL_DISABLE_SECMEM);
+ gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
}
-uint16_t dnssec_keytag(DnsResourceRecord *dnskey) {
+uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke) {
const uint8_t *p;
- uint32_t sum;
+ uint32_t sum, f;
size_t i;
/* The algorithm from RFC 4034, Appendix B. */
@@ -88,8 +91,12 @@ uint16_t dnssec_keytag(DnsResourceRecord *dnskey) {
assert(dnskey);
assert(dnskey->key->type == DNS_TYPE_DNSKEY);
- sum = (uint32_t) dnskey->dnskey.flags +
- ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
+ f = (uint32_t) dnskey->dnskey.flags;
+
+ if (mask_revoke)
+ f &= ~DNSKEY_FLAG_REVOKE;
+
+ sum = f + ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
p = dnskey->dnskey.key;
@@ -115,21 +122,21 @@ static int rr_compare(const void *a, const void *b) {
assert(*y);
assert((*y)->wire_format);
- m = MIN((*x)->wire_format_size, (*y)->wire_format_size);
+ m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x), DNS_RESOURCE_RECORD_RDATA_SIZE(*y));
- r = memcmp((*x)->wire_format, (*y)->wire_format, m);
+ r = memcmp(DNS_RESOURCE_RECORD_RDATA(*x), DNS_RESOURCE_RECORD_RDATA(*y), m);
if (r != 0)
return r;
- if ((*x)->wire_format_size < (*y)->wire_format_size)
+ if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
return -1;
- else if ((*x)->wire_format_size > (*y)->wire_format_size)
+ else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y))
return 1;
return 0;
}
-static int dnssec_rsa_verify(
+static int dnssec_rsa_verify_raw(
const char *hash_algorithm,
const void *signature, size_t signature_size,
const void *data, size_t data_size,
@@ -193,11 +200,12 @@ static int dnssec_rsa_verify(
}
ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
- if (ge == GPG_ERR_BAD_SIGNATURE)
+ if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE)
r = 0;
- else if (ge != 0)
+ else if (ge != 0) {
+ log_debug("RSA signature check failed: %s", gpg_strerror(ge));
r = -EIO;
- else
+ } else
r = 1;
finish:
@@ -218,6 +226,196 @@ finish:
return r;
}
+static int dnssec_rsa_verify(
+ const char *hash_algorithm,
+ const void *hash, size_t hash_size,
+ DnsResourceRecord *rrsig,
+ DnsResourceRecord *dnskey) {
+
+ size_t exponent_size, modulus_size;
+ void *exponent, *modulus;
+
+ assert(hash_algorithm);
+ assert(hash);
+ assert(hash_size > 0);
+ assert(rrsig);
+ assert(dnskey);
+
+ if (*(uint8_t*) dnskey->dnskey.key == 0) {
+ /* exponent is > 255 bytes long */
+
+ exponent = (uint8_t*) dnskey->dnskey.key + 3;
+ exponent_size =
+ ((size_t) (((uint8_t*) dnskey->dnskey.key)[1]) << 8) |
+ ((size_t) ((uint8_t*) dnskey->dnskey.key)[2]);
+
+ if (exponent_size < 256)
+ return -EINVAL;
+
+ if (3 + exponent_size >= dnskey->dnskey.key_size)
+ return -EINVAL;
+
+ modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size;
+ modulus_size = dnskey->dnskey.key_size - 3 - exponent_size;
+
+ } else {
+ /* exponent is <= 255 bytes long */
+
+ exponent = (uint8_t*) dnskey->dnskey.key + 1;
+ exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0];
+
+ if (exponent_size <= 0)
+ return -EINVAL;
+
+ if (1 + exponent_size >= dnskey->dnskey.key_size)
+ return -EINVAL;
+
+ modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size;
+ modulus_size = dnskey->dnskey.key_size - 1 - exponent_size;
+ }
+
+ return dnssec_rsa_verify_raw(
+ hash_algorithm,
+ rrsig->rrsig.signature, rrsig->rrsig.signature_size,
+ hash, hash_size,
+ exponent, exponent_size,
+ modulus, modulus_size);
+}
+
+static int dnssec_ecdsa_verify_raw(
+ const char *hash_algorithm,
+ const char *curve,
+ const void *signature_r, size_t signature_r_size,
+ const void *signature_s, size_t signature_s_size,
+ const void *data, size_t data_size,
+ const void *key, size_t key_size) {
+
+ gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
+ gcry_mpi_t q = NULL, r = NULL, s = NULL;
+ gcry_error_t ge;
+ int k;
+
+ assert(hash_algorithm);
+
+ ge = gcry_mpi_scan(&r, GCRYMPI_FMT_USG, signature_r, signature_r_size, NULL);
+ if (ge != 0) {
+ k = -EIO;
+ goto finish;
+ }
+
+ ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature_s, signature_s_size, NULL);
+ if (ge != 0) {
+ k = -EIO;
+ goto finish;
+ }
+
+ ge = gcry_mpi_scan(&q, GCRYMPI_FMT_USG, key, key_size, NULL);
+ if (ge != 0) {
+ k = -EIO;
+ goto finish;
+ }
+
+ ge = gcry_sexp_build(&signature_sexp,
+ NULL,
+ "(sig-val (ecdsa (r %m) (s %m)))",
+ r,
+ s);
+ if (ge != 0) {
+ k = -EIO;
+ goto finish;
+ }
+
+ ge = gcry_sexp_build(&data_sexp,
+ NULL,
+ "(data (flags rfc6979) (hash %s %b))",
+ hash_algorithm,
+ (int) data_size,
+ data);
+ if (ge != 0) {
+ k = -EIO;
+ goto finish;
+ }
+
+ ge = gcry_sexp_build(&public_key_sexp,
+ NULL,
+ "(public-key (ecc (curve %s) (q %m)))",
+ curve,
+ q);
+ if (ge != 0) {
+ k = -EIO;
+ goto finish;
+ }
+
+ ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
+ if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE)
+ k = 0;
+ else if (ge != 0) {
+ log_debug("ECDSA signature check failed: %s", gpg_strerror(ge));
+ k = -EIO;
+ } else
+ k = 1;
+finish:
+ if (r)
+ gcry_mpi_release(r);
+ if (s)
+ gcry_mpi_release(s);
+ if (q)
+ gcry_mpi_release(q);
+
+ if (public_key_sexp)
+ gcry_sexp_release(public_key_sexp);
+ if (signature_sexp)
+ gcry_sexp_release(signature_sexp);
+ if (data_sexp)
+ gcry_sexp_release(data_sexp);
+
+ return k;
+}
+
+static int dnssec_ecdsa_verify(
+ const char *hash_algorithm,
+ int algorithm,
+ const void *hash, size_t hash_size,
+ DnsResourceRecord *rrsig,
+ DnsResourceRecord *dnskey) {
+
+ const char *curve;
+ size_t key_size;
+ uint8_t *q;
+
+ assert(hash);
+ assert(hash_size);
+ assert(rrsig);
+ assert(dnskey);
+
+ if (algorithm == DNSSEC_ALGORITHM_ECDSAP256SHA256) {
+ key_size = 32;
+ curve = "NIST P-256";
+ } else if (algorithm == DNSSEC_ALGORITHM_ECDSAP384SHA384) {
+ key_size = 48;
+ curve = "NIST P-384";
+ } else
+ return -EOPNOTSUPP;
+
+ if (dnskey->dnskey.key_size != key_size * 2)
+ return -EINVAL;
+
+ if (rrsig->rrsig.signature_size != key_size * 2)
+ return -EINVAL;
+
+ q = alloca(key_size*2 + 1);
+ q[0] = 0x04; /* Prepend 0x04 to indicate an uncompressed key */
+ memcpy(q+1, dnskey->dnskey.key, key_size*2);
+
+ return dnssec_ecdsa_verify_raw(
+ hash_algorithm,
+ curve,
+ rrsig->rrsig.signature, key_size,
+ (uint8_t*) rrsig->rrsig.signature + key_size, key_size,
+ hash, hash_size,
+ q, key_size*2+1);
+}
+
static void md_add_uint8(gcry_md_hd_t md, uint8_t v) {
gcry_md_write(md, &v, sizeof(v));
}
@@ -267,24 +465,60 @@ static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) {
return realtime < inception || realtime > expiration;
}
+static int algorithm_to_gcrypt_md(uint8_t algorithm) {
+
+ /* Translates a DNSSEC signature algorithm into a gcrypt
+ * digest identifier.
+ *
+ * Note that we implement all algorithms listed as "Must
+ * implement" and "Recommended to Implement" in RFC6944. We
+ * don't implement any algorithms that are listed as
+ * "Optional" or "Must Not Implement". Specifically, we do not
+ * implement RSAMD5, DSASHA1, DH, DSA-NSEC3-SHA1, and
+ * GOST-ECC. */
+
+ switch (algorithm) {
+
+ case DNSSEC_ALGORITHM_RSASHA1:
+ case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1:
+ return GCRY_MD_SHA1;
+
+ case DNSSEC_ALGORITHM_RSASHA256:
+ case DNSSEC_ALGORITHM_ECDSAP256SHA256:
+ return GCRY_MD_SHA256;
+
+ case DNSSEC_ALGORITHM_ECDSAP384SHA384:
+ return GCRY_MD_SHA384;
+
+ case DNSSEC_ALGORITHM_RSASHA512:
+ return GCRY_MD_SHA512;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
int dnssec_verify_rrset(
DnsAnswer *a,
- DnsResourceKey *key,
+ const DnsResourceKey *key,
DnsResourceRecord *rrsig,
DnsResourceRecord *dnskey,
- usec_t realtime) {
+ usec_t realtime,
+ DnssecResult *result) {
uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX];
- size_t exponent_size, modulus_size, hash_size;
- void *exponent, *modulus, *hash;
+ size_t hash_size;
+ void *hash;
DnsResourceRecord **list, *rr;
gcry_md_hd_t md = NULL;
+ int r, md_algorithm;
+ bool wildcard = false;
size_t k, n = 0;
- int r;
assert(key);
assert(rrsig);
assert(dnskey);
+ assert(result);
assert(rrsig->key->type == DNS_TYPE_RRSIG);
assert(dnskey->key->type == DNS_TYPE_DNSKEY);
@@ -292,20 +526,24 @@ int dnssec_verify_rrset(
* using the signature "rrsig" and the key "dnskey". It's
* assumed the RRSIG and DNSKEY match. */
- if (!dnssec_algorithm_supported(rrsig->rrsig.algorithm))
- return -EOPNOTSUPP;
-
- if (a->n_rrs > VERIFY_RRS_MAX)
- return -E2BIG;
+ md_algorithm = algorithm_to_gcrypt_md(rrsig->rrsig.algorithm);
+ if (md_algorithm == -EOPNOTSUPP) {
+ *result = DNSSEC_UNSUPPORTED_ALGORITHM;
+ return 0;
+ }
+ if (md_algorithm < 0)
+ return md_algorithm;
r = dnssec_rrsig_expired(rrsig, realtime);
if (r < 0)
return r;
- if (r > 0)
- return DNSSEC_SIGNATURE_EXPIRED;
+ if (r > 0) {
+ *result = DNSSEC_SIGNATURE_EXPIRED;
+ return 0;
+ }
/* Collect all relevant RRs in a single array, so that we can look at the RRset */
- list = newa(DnsResourceRecord *, a->n_rrs);
+ list = newa(DnsResourceRecord *, dns_answer_size(a));
DNS_ANSWER_FOREACH(rr, a) {
r = dns_resource_key_equal(key, rr->key);
@@ -320,37 +558,24 @@ int dnssec_verify_rrset(
return r;
list[n++] = rr;
+
+ if (n > VERIFY_RRS_MAX)
+ return -E2BIG;
}
if (n <= 0)
return -ENODATA;
/* Bring the RRs into canonical order */
- qsort_safe(list, n, sizeof(DnsResourceRecord), rr_compare);
+ qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare);
/* OK, the RRs are now in canonical order. Let's calculate the digest */
- switch (rrsig->rrsig.algorithm) {
-
- case DNSSEC_ALGORITHM_RSASHA1:
- case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1:
- gcry_md_open(&md, GCRY_MD_SHA1, 0);
- hash_size = 20;
- break;
+ initialize_libgcrypt();
- case DNSSEC_ALGORITHM_RSASHA256:
- gcry_md_open(&md, GCRY_MD_SHA256, 0);
- hash_size = 32;
- break;
-
- case DNSSEC_ALGORITHM_RSASHA512:
- gcry_md_open(&md, GCRY_MD_SHA512, 0);
- hash_size = 64;
- break;
-
- default:
- assert_not_reached("Unknown digest");
- }
+ hash_size = gcry_md_get_algo_dlen(md_algorithm);
+ assert(hash_size > 0);
+ gcry_md_open(&md, md_algorithm, 0);
if (!md)
return -EIO;
@@ -368,10 +593,19 @@ int dnssec_verify_rrset(
gcry_md_write(md, wire_format_name, r);
for (k = 0; k < n; k++) {
+ const char *suffix;
size_t l;
rr = list[k];
- r = dns_name_to_wire_format(DNS_RESOURCE_KEY_NAME(rr->key), wire_format_name, sizeof(wire_format_name), true);
+ r = dns_name_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rrsig->rrsig.labels, &suffix);
+ if (r < 0)
+ goto finish;
+ if (r > 0) /* This is a wildcard! */ {
+ gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2);
+ wildcard = true;
+ }
+
+ r = dns_name_to_wire_format(suffix, wire_format_name, sizeof(wire_format_name), true);
if (r < 0)
goto finish;
gcry_md_write(md, wire_format_name, r);
@@ -380,12 +614,11 @@ int dnssec_verify_rrset(
md_add_uint16(md, rr->key->class);
md_add_uint32(md, rrsig->rrsig.original_ttl);
- assert(rr->wire_format_rdata_offset <= rr->wire_format_size);
- l = rr->wire_format_size - rr->wire_format_rdata_offset;
+ l = DNS_RESOURCE_RECORD_RDATA_SIZE(rr);
assert(l <= 0xFFFF);
md_add_uint16(md, (uint16_t) l);
- gcry_md_write(md, (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset, l);
+ gcry_md_write(md, DNS_RESOURCE_RECORD_RDATA(rr), l);
}
hash = gcry_md_read(md, 0);
@@ -394,64 +627,47 @@ int dnssec_verify_rrset(
goto finish;
}
- if (*(uint8_t*) dnskey->dnskey.key == 0) {
- /* exponent is > 255 bytes long */
-
- exponent = (uint8_t*) dnskey->dnskey.key + 3;
- exponent_size =
- ((size_t) (((uint8_t*) dnskey->dnskey.key)[0]) << 8) |
- ((size_t) ((uint8_t*) dnskey->dnskey.key)[1]);
-
- if (exponent_size < 256) {
- r = -EINVAL;
- goto finish;
- }
-
- if (3 + exponent_size >= dnskey->dnskey.key_size) {
- r = -EINVAL;
- goto finish;
- }
-
- modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size;
- modulus_size = dnskey->dnskey.key_size - 3 - exponent_size;
-
- } else {
- /* exponent is <= 255 bytes long */
-
- exponent = (uint8_t*) dnskey->dnskey.key + 1;
- exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0];
-
- if (exponent_size <= 0) {
- r = -EINVAL;
- goto finish;
- }
+ switch (rrsig->rrsig.algorithm) {
- if (1 + exponent_size >= dnskey->dnskey.key_size) {
- r = -EINVAL;
- goto finish;
- }
+ case DNSSEC_ALGORITHM_RSASHA1:
+ case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1:
+ case DNSSEC_ALGORITHM_RSASHA256:
+ case DNSSEC_ALGORITHM_RSASHA512:
+ r = dnssec_rsa_verify(
+ gcry_md_algo_name(md_algorithm),
+ hash, hash_size,
+ rrsig,
+ dnskey);
+ break;
- modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size;
- modulus_size = dnskey->dnskey.key_size - 1 - exponent_size;
+ case DNSSEC_ALGORITHM_ECDSAP256SHA256:
+ case DNSSEC_ALGORITHM_ECDSAP384SHA384:
+ r = dnssec_ecdsa_verify(
+ gcry_md_algo_name(md_algorithm),
+ rrsig->rrsig.algorithm,
+ hash, hash_size,
+ rrsig,
+ dnskey);
+ break;
}
- r = dnssec_rsa_verify(
- gcry_md_algo_name(gcry_md_get_algo(md)),
- rrsig->rrsig.signature, rrsig->rrsig.signature_size,
- hash, hash_size,
- exponent, exponent_size,
- modulus, modulus_size);
if (r < 0)
goto finish;
- r = r ? DNSSEC_VERIFIED : DNSSEC_INVALID;
+ if (!r)
+ *result = DNSSEC_INVALID;
+ else if (wildcard)
+ *result = DNSSEC_VALIDATED_WILDCARD;
+ else
+ *result = DNSSEC_VALIDATED;
+ r = 0;
finish:
gcry_md_close(md);
return r;
}
-int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) {
+int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) {
assert(rrsig);
assert(dnskey);
@@ -468,18 +684,22 @@ int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnske
return 0;
if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
return 0;
+ if (!revoked_ok && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE))
+ return 0;
if (dnskey->dnskey.protocol != 3)
return 0;
if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm)
return 0;
- if (dnssec_keytag(dnskey) != rrsig->rrsig.key_tag)
+ if (dnssec_keytag(dnskey, false) != rrsig->rrsig.key_tag)
return 0;
- return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), DNS_RESOURCE_KEY_NAME(rrsig->key));
+ return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), rrsig->rrsig.signer);
}
-int dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig) {
+int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) {
+ int r;
+
assert(key);
assert(rrsig);
@@ -492,22 +712,61 @@ int dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig) {
if (rrsig->rrsig.type_covered != key->type)
return 0;
+ /* Make sure signer is a parent of the RRset */
+ r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rrsig->key), rrsig->rrsig.signer);
+ if (r <= 0)
+ return r;
+
+ /* Make sure the owner name has at least as many labels as the "label" fields indicates. */
+ r = dns_name_count_labels(DNS_RESOURCE_KEY_NAME(rrsig->key));
+ if (r < 0)
+ return r;
+ if (r < rrsig->rrsig.labels)
+ return 0;
+
return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key));
}
+static int dnssec_fix_rrset_ttl(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord *rrsig, usec_t realtime) {
+ DnsResourceRecord *rr;
+ int r;
+
+ assert(key);
+ assert(rrsig);
+
+ DNS_ANSWER_FOREACH(rr, a) {
+ r = dns_resource_key_equal(key, rr->key);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ /* Pick the TTL as the minimum of the RR's TTL, the
+ * RR's original TTL according to the RRSIG and the
+ * RRSIG's own TTL, see RFC 4035, Section 5.3.3 */
+ rr->ttl = MIN3(rr->ttl, rrsig->rrsig.original_ttl, rrsig->ttl);
+ rr->expiry = rrsig->rrsig.expiration * USEC_PER_SEC;
+ }
+
+ return 0;
+}
+
int dnssec_verify_rrset_search(
DnsAnswer *a,
- DnsResourceKey *key,
+ const DnsResourceKey *key,
DnsAnswer *validated_dnskeys,
- usec_t realtime) {
+ usec_t realtime,
+ DnssecResult *result,
+ DnsResourceRecord **ret_rrsig) {
- bool found_rrsig = false, found_dnskey = false;
+ bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false;
DnsResourceRecord *rrsig;
int r;
assert(key);
+ assert(result);
- /* Verifies all RRs from "a" that match the key "key", against DNSKEY RRs in "validated_dnskeys" */
+ /* Verifies all RRs from "a" that match the key "key" against DNSKEYs in "validated_dnskeys" */
if (!a || a->n_rrs <= 0)
return -ENODATA;
@@ -515,7 +774,9 @@ int dnssec_verify_rrset_search(
/* Iterate through each RRSIG RR. */
DNS_ANSWER_FOREACH(rrsig, a) {
DnsResourceRecord *dnskey;
+ DnsAnswerFlags flags;
+ /* Is this an RRSIG RR that applies to RRs matching our key? */
r = dnssec_key_match_rrsig(key, rrsig);
if (r < 0)
return r;
@@ -524,16 +785,20 @@ int dnssec_verify_rrset_search(
found_rrsig = true;
- DNS_ANSWER_FOREACH(dnskey, validated_dnskeys) {
+ /* Look for a matching key */
+ DNS_ANSWER_FOREACH_FLAGS(dnskey, flags, validated_dnskeys) {
+ DnssecResult one_result;
+
+ if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
+ continue;
- r = dnssec_rrsig_match_dnskey(rrsig, dnskey);
+ /* Is this a DNSKEY RR that matches they key of our RRSIG? */
+ r = dnssec_rrsig_match_dnskey(rrsig, dnskey, false);
if (r < 0)
return r;
if (r == 0)
continue;
- found_dnskey = true;
-
/* Take the time here, if it isn't set yet, so
* that we do all validations with the same
* time. */
@@ -545,27 +810,89 @@ int dnssec_verify_rrset_search(
* the RRSet against the RRSIG and DNSKEY
* combination. */
- r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime);
- if (r < 0 && r != EOPNOTSUPP)
+ r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime, &one_result);
+ if (r < 0)
return r;
- if (r == DNSSEC_VERIFIED)
- return DNSSEC_VERIFIED;
-
- /* If the signature is invalid, or done using
- an unsupported algorithm, let's try another
- key and/or signature. After all they
- key_tags and stuff are not unique, and
- might be shared by multiple keys. */
+
+ switch (one_result) {
+
+ case DNSSEC_VALIDATED:
+ case DNSSEC_VALIDATED_WILDCARD:
+ /* Yay, the RR has been validated,
+ * return immediately, but fix up the expiry */
+ r = dnssec_fix_rrset_ttl(a, key, rrsig, realtime);
+ if (r < 0)
+ return r;
+
+ if (ret_rrsig)
+ *ret_rrsig = rrsig;
+
+ *result = one_result;
+ return 0;
+
+ case DNSSEC_INVALID:
+ /* If the signature is invalid, let's try another
+ key and/or signature. After all they
+ key_tags and stuff are not unique, and
+ might be shared by multiple keys. */
+ found_invalid = true;
+ continue;
+
+ case DNSSEC_UNSUPPORTED_ALGORITHM:
+ /* If the key algorithm is
+ unsupported, try another
+ RRSIG/DNSKEY pair, but remember we
+ encountered this, so that we can
+ return a proper error when we
+ encounter nothing better. */
+ found_unsupported_algorithm = true;
+ continue;
+
+ case DNSSEC_SIGNATURE_EXPIRED:
+ /* If the signature is expired, try
+ another one, but remember it, so
+ that we can return this */
+ found_expired_rrsig = true;
+ continue;
+
+ default:
+ assert_not_reached("Unexpected DNSSEC validation result");
+ }
}
}
- if (found_dnskey)
- return DNSSEC_INVALID;
+ if (found_expired_rrsig)
+ *result = DNSSEC_SIGNATURE_EXPIRED;
+ else if (found_unsupported_algorithm)
+ *result = DNSSEC_UNSUPPORTED_ALGORITHM;
+ else if (found_invalid)
+ *result = DNSSEC_INVALID;
+ else if (found_rrsig)
+ *result = DNSSEC_MISSING_KEY;
+ else
+ *result = DNSSEC_NO_SIGNATURE;
+
+ if (ret_rrsig)
+ *ret_rrsig = NULL;
+
+ return 0;
+}
+
+int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) {
+ DnsResourceRecord *rr;
+ int r;
- if (found_rrsig)
- return DNSSEC_MISSING_KEY;
+ /* Checks whether there's at least one RRSIG in 'a' that proctects RRs of the specified key */
- return DNSSEC_NO_SIGNATURE;
+ DNS_ANSWER_FOREACH(rr, a) {
+ r = dnssec_key_match_rrsig(key, rr);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return 1;
+ }
+
+ return 0;
}
int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
@@ -579,8 +906,6 @@ int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
return -ENOBUFS;
for (;;) {
- size_t i;
-
r = dns_label_unescape(&n, buffer, buffer_max);
if (r < 0)
return r;
@@ -607,11 +932,7 @@ int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
if (memchr(buffer, '.', r))
return -EINVAL;
- for (i = 0; i < (size_t) r; i ++) {
- if (buffer[i] >= 'A' && buffer[i] <= 'Z')
- buffer[i] = buffer[i] - 'A' + 'a';
- }
-
+ ascii_strlower_n(buffer, (size_t) r);
buffer[r] = '.';
buffer += r + 1;
@@ -633,11 +954,32 @@ int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
return (int) c;
}
-int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
- gcry_md_hd_t md = NULL;
+static int digest_to_gcrypt_md(uint8_t algorithm) {
+
+ /* Translates a DNSSEC digest algorithm into a gcrypt digest identifier */
+
+ switch (algorithm) {
+
+ case DNSSEC_DIGEST_SHA1:
+ return GCRY_MD_SHA1;
+
+ case DNSSEC_DIGEST_SHA256:
+ return GCRY_MD_SHA256;
+
+ case DNSSEC_DIGEST_SHA384:
+ return GCRY_MD_SHA384;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) {
char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX];
+ gcry_md_hd_t md = NULL;
+ size_t hash_size;
+ int md_algorithm, r;
void *result;
- int r;
assert(dnskey);
assert(ds);
@@ -650,50 +992,41 @@ int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
return -EINVAL;
if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
return -EKEYREJECTED;
+ if (!mask_revoke && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE))
+ return -EKEYREJECTED;
if (dnskey->dnskey.protocol != 3)
return -EKEYREJECTED;
- if (!dnssec_algorithm_supported(dnskey->dnskey.algorithm))
- return -EOPNOTSUPP;
- if (!dnssec_digest_supported(ds->ds.digest_type))
- return -EOPNOTSUPP;
-
if (dnskey->dnskey.algorithm != ds->ds.algorithm)
return 0;
- if (dnssec_keytag(dnskey) != ds->ds.key_tag)
+ if (dnssec_keytag(dnskey, mask_revoke) != ds->ds.key_tag)
return 0;
- switch (ds->ds.digest_type) {
-
- case DNSSEC_DIGEST_SHA1:
-
- if (ds->ds.digest_size != 20)
- return 0;
-
- gcry_md_open(&md, GCRY_MD_SHA1, 0);
- break;
+ initialize_libgcrypt();
- case DNSSEC_DIGEST_SHA256:
+ md_algorithm = digest_to_gcrypt_md(ds->ds.digest_type);
+ if (md_algorithm < 0)
+ return md_algorithm;
- if (ds->ds.digest_size != 32)
- return 0;
+ hash_size = gcry_md_get_algo_dlen(md_algorithm);
+ assert(hash_size > 0);
- gcry_md_open(&md, GCRY_MD_SHA256, 0);
- break;
+ if (ds->ds.digest_size != hash_size)
+ return 0;
- default:
- assert_not_reached("Unknown digest");
- }
+ r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name));
+ if (r < 0)
+ return r;
+ gcry_md_open(&md, md_algorithm, 0);
if (!md)
return -EIO;
- r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name));
- if (r < 0)
- goto finish;
-
gcry_md_write(md, owner_name, r);
- md_add_uint16(md, dnskey->dnskey.flags);
+ if (mask_revoke)
+ md_add_uint16(md, dnskey->dnskey.flags & ~DNSKEY_FLAG_REVOKE);
+ else
+ md_add_uint16(md, dnskey->dnskey.flags);
md_add_uint8(md, dnskey->dnskey.protocol);
md_add_uint8(md, dnskey->dnskey.algorithm);
gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size);
@@ -711,9 +1044,640 @@ finish:
return r;
}
-static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = {
- [DNSSEC_NO] = "no",
- [DNSSEC_TRUST] = "trust",
- [DNSSEC_YES] = "yes",
+int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) {
+ DnsResourceRecord *ds;
+ DnsAnswerFlags flags;
+ int r;
+
+ assert(dnskey);
+
+ if (dnskey->key->type != DNS_TYPE_DNSKEY)
+ return 0;
+
+ DNS_ANSWER_FOREACH_FLAGS(ds, flags, validated_ds) {
+
+ if ((flags & DNS_ANSWER_AUTHENTICATED) == 0)
+ continue;
+
+ if (ds->key->type != DNS_TYPE_DS)
+ continue;
+
+ if (ds->key->class != dnskey->key->class)
+ continue;
+
+ r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), DNS_RESOURCE_KEY_NAME(ds->key));
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ r = dnssec_verify_dnskey(dnskey, ds, false);
+ if (r == -EKEYREJECTED)
+ return 0; /* The DNSKEY is revoked or otherwise invalid, we won't bless it */
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+static int nsec3_hash_to_gcrypt_md(uint8_t algorithm) {
+
+ /* Translates a DNSSEC NSEC3 hash algorithm into a gcrypt digest identifier */
+
+ switch (algorithm) {
+
+ case NSEC3_ALGORITHM_SHA1:
+ return GCRY_MD_SHA1;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) {
+ uint8_t wire_format[DNS_WIRE_FOMAT_HOSTNAME_MAX];
+ gcry_md_hd_t md = NULL;
+ size_t hash_size;
+ int algorithm;
+ void *result;
+ unsigned k;
+ int r;
+
+ assert(nsec3);
+ assert(name);
+ assert(ret);
+
+ if (nsec3->key->type != DNS_TYPE_NSEC3)
+ return -EINVAL;
+
+ if (nsec3->nsec3.iterations > NSEC3_ITERATIONS_MAX) {
+ log_debug("Ignoring NSEC3 RR %s with excessive number of iterations.", dns_resource_record_to_string(nsec3));
+ return -EOPNOTSUPP;
+ }
+
+ algorithm = nsec3_hash_to_gcrypt_md(nsec3->nsec3.algorithm);
+ if (algorithm < 0)
+ return algorithm;
+
+ initialize_libgcrypt();
+
+ hash_size = gcry_md_get_algo_dlen(algorithm);
+ assert(hash_size > 0);
+
+ if (nsec3->nsec3.next_hashed_name_size != hash_size)
+ return -EINVAL;
+
+ r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true);
+ if (r < 0)
+ return r;
+
+ gcry_md_open(&md, algorithm, 0);
+ if (!md)
+ return -EIO;
+
+ gcry_md_write(md, wire_format, r);
+ gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size);
+
+ result = gcry_md_read(md, 0);
+ if (!result) {
+ r = -EIO;
+ goto finish;
+ }
+
+ for (k = 0; k < nsec3->nsec3.iterations; k++) {
+ uint8_t tmp[hash_size];
+ memcpy(tmp, result, hash_size);
+
+ gcry_md_reset(md);
+ gcry_md_write(md, tmp, hash_size);
+ gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size);
+
+ result = gcry_md_read(md, 0);
+ if (!result) {
+ r = -EIO;
+ goto finish;
+ }
+ }
+
+ memcpy(ret, result, hash_size);
+ r = (int) hash_size;
+
+finish:
+ gcry_md_close(md);
+ return r;
+}
+
+static int nsec3_is_good(DnsResourceRecord *rr, DnsResourceRecord *nsec3) {
+ const char *a, *b;
+ int r;
+
+ assert(rr);
+
+ if (rr->key->type != DNS_TYPE_NSEC3)
+ return 0;
+
+ /* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */
+ if (!IN_SET(rr->nsec3.flags, 0, 1))
+ return 0;
+
+ /* Ignore NSEC3 RRs whose algorithm we don't know */
+ if (nsec3_hash_to_gcrypt_md(rr->nsec3.algorithm) < 0)
+ return 0;
+ /* Ignore NSEC3 RRs with an excessive number of required iterations */
+ if (rr->nsec3.iterations > NSEC3_ITERATIONS_MAX)
+ return 0;
+
+ if (!nsec3)
+ return 1;
+
+ /* If a second NSEC3 RR is specified, also check if they are from the same zone. */
+
+ if (nsec3 == rr) /* Shortcut */
+ return 1;
+
+ if (rr->key->class != nsec3->key->class)
+ return 0;
+ if (rr->nsec3.algorithm != nsec3->nsec3.algorithm)
+ return 0;
+ if (rr->nsec3.iterations != nsec3->nsec3.iterations)
+ return 0;
+ if (rr->nsec3.salt_size != nsec3->nsec3.salt_size)
+ return 0;
+ if (memcmp(rr->nsec3.salt, nsec3->nsec3.salt, rr->nsec3.salt_size) != 0)
+ return 0;
+
+ a = DNS_RESOURCE_KEY_NAME(rr->key);
+ r = dns_name_parent(&a); /* strip off hash */
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 0;
+
+ b = DNS_RESOURCE_KEY_NAME(nsec3->key);
+ r = dns_name_parent(&b); /* strip off hash */
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 0;
+
+ return dns_name_equal(a, b);
+}
+
+static int nsec3_hashed_domain_format(const uint8_t *hashed, size_t hashed_size, const char *zone, char **ret) {
+ _cleanup_free_ char *l = NULL;
+ char *j;
+
+ assert(hashed);
+ assert(hashed_size > 0);
+ assert(zone);
+ assert(ret);
+
+ l = base32hexmem(hashed, hashed_size, false);
+ if (!l)
+ return -ENOMEM;
+
+ j = strjoin(l, ".", zone, NULL);
+ if (!j)
+ return -ENOMEM;
+
+ *ret = j;
+ return (int) hashed_size;
+}
+
+static int nsec3_hashed_domain_make(DnsResourceRecord *nsec3, const char *domain, const char *zone, char **ret) {
+ uint8_t hashed[DNSSEC_HASH_SIZE_MAX];
+ int hashed_size;
+
+ assert(nsec3);
+ assert(domain);
+ assert(zone);
+ assert(ret);
+
+ hashed_size = dnssec_nsec3_hash(nsec3, domain, hashed);
+ if (hashed_size < 0)
+ return hashed_size;
+
+ return nsec3_hashed_domain_format(hashed, (size_t) hashed_size, zone, ret);
+}
+
+/* See RFC 5155, Section 8
+ * First try to find a NSEC3 record that matches our query precisely, if that fails, find the closest
+ * enclosure. Secondly, find a proof that there is no closer enclosure and either a proof that there
+ * is no wildcard domain as a direct descendant of the closest enclosure, or find an NSEC3 record that
+ * matches the wildcard domain.
+ *
+ * Based on this we can prove either the existence of the record in @key, or NXDOMAIN or NODATA, or
+ * that there is no proof either way. The latter is the case if a the proof of non-existence of a given
+ * name uses an NSEC3 record with the opt-out bit set. Lastly, if we are given insufficient NSEC3 records
+ * to conclude anything we indicate this by returning NO_RR. */
+static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) {
+ _cleanup_free_ char *next_closer_domain = NULL, *wildcard = NULL, *wildcard_domain = NULL;
+ const char *zone, *p, *pp = NULL;
+ DnsResourceRecord *rr, *enclosure_rr, *zone_rr, *wildcard_rr = NULL;
+ DnsAnswerFlags flags;
+ int hashed_size, r;
+ bool a, no_closer = false, no_wildcard = false, optout = false;
+
+ assert(key);
+ assert(result);
+
+ /* First step, find the zone name and the NSEC3 parameters of the zone.
+ * it is sufficient to look for the longest common suffix we find with
+ * any NSEC3 RR in the response. Any NSEC3 record will do as all NSEC3
+ * records from a given zone in a response must use the same
+ * parameters. */
+ zone = DNS_RESOURCE_KEY_NAME(key);
+ for (;;) {
+ DNS_ANSWER_FOREACH_FLAGS(zone_rr, flags, answer) {
+ r = nsec3_is_good(zone_rr, NULL);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ r = dns_name_equal_skip(DNS_RESOURCE_KEY_NAME(zone_rr->key), 1, zone);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ goto found_zone;
+ }
+
+ /* Strip one label from the front */
+ r = dns_name_parent(&zone);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+ }
+
+ *result = DNSSEC_NSEC_NO_RR;
+ return 0;
+
+found_zone:
+ /* Second step, find the closest encloser NSEC3 RR in 'answer' that matches 'key' */
+ p = DNS_RESOURCE_KEY_NAME(key);
+ for (;;) {
+ _cleanup_free_ char *hashed_domain = NULL;
+
+ hashed_size = nsec3_hashed_domain_make(zone_rr, p, zone, &hashed_domain);
+ if (hashed_size == -EOPNOTSUPP) {
+ *result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM;
+ return 0;
+ }
+ if (hashed_size < 0)
+ return hashed_size;
+
+ DNS_ANSWER_FOREACH_FLAGS(enclosure_rr, flags, answer) {
+
+ r = nsec3_is_good(enclosure_rr, zone_rr);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ if (enclosure_rr->nsec3.next_hashed_name_size != (size_t) hashed_size)
+ continue;
+
+ r = dns_name_equal(DNS_RESOURCE_KEY_NAME(enclosure_rr->key), hashed_domain);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ a = flags & DNS_ANSWER_AUTHENTICATED;
+ goto found_closest_encloser;
+ }
+ }
+
+ /* We didn't find the closest encloser with this name,
+ * but let's remember this domain name, it might be
+ * the next closer name */
+
+ pp = p;
+
+ /* Strip one label from the front */
+ r = dns_name_parent(&p);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+ }
+
+ *result = DNSSEC_NSEC_NO_RR;
+ return 0;
+
+found_closest_encloser:
+ /* We found a closest encloser in 'p'; next closer is 'pp' */
+
+ /* Ensure this is not a DNAME domain, see RFC5155, section 8.3. */
+ if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_DNAME))
+ return -EBADMSG;
+
+ /* Ensure that this data is from the delegated domain
+ * (i.e. originates from the "lower" DNS server), and isn't
+ * just glue records (i.e. doesn't originate from the "upper"
+ * DNS server). */
+ if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_NS) &&
+ !bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA))
+ return -EBADMSG;
+
+ if (!pp) {
+ /* No next closer NSEC3 RR. That means there's a direct NSEC3 RR for our key. */
+ if (bitmap_isset(enclosure_rr->nsec3.types, key->type))
+ *result = DNSSEC_NSEC_FOUND;
+ else if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_CNAME))
+ *result = DNSSEC_NSEC_CNAME;
+ else
+ *result = DNSSEC_NSEC_NODATA;
+
+ if (authenticated)
+ *authenticated = a;
+ if (ttl)
+ *ttl = enclosure_rr->ttl;
+
+ return 0;
+ }
+
+ /* Prove that there is no next closer and whether or not there is a wildcard domain. */
+
+ wildcard = strappend("*.", p);
+ if (!wildcard)
+ return -ENOMEM;
+
+ r = nsec3_hashed_domain_make(enclosure_rr, wildcard, zone, &wildcard_domain);
+ if (r < 0)
+ return r;
+ if (r != hashed_size)
+ return -EBADMSG;
+
+ r = nsec3_hashed_domain_make(enclosure_rr, pp, zone, &next_closer_domain);
+ if (r < 0)
+ return r;
+ if (r != hashed_size)
+ return -EBADMSG;
+
+ DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
+ _cleanup_free_ char *next_hashed_domain = NULL;
+
+ r = nsec3_is_good(rr, zone_rr);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ r = nsec3_hashed_domain_format(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, zone, &next_hashed_domain);
+ if (r < 0)
+ return r;
+
+ r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), next_closer_domain, next_hashed_domain);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ if (rr->nsec3.flags & 1)
+ optout = true;
+
+ a = a && (flags & DNS_ANSWER_AUTHENTICATED);
+
+ no_closer = true;
+ }
+
+ r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), wildcard_domain);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ a = a && (flags & DNS_ANSWER_AUTHENTICATED);
+
+ wildcard_rr = rr;
+ }
+
+ r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), wildcard_domain, next_hashed_domain);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ if (rr->nsec3.flags & 1)
+ /* This only makes sense if we have a wildcard delegation, which is
+ * very unlikely, see RFC 4592, Section 4.2, but we cannot rely on
+ * this not happening, so hence cannot simply conclude NXDOMAIN as
+ * we would wish */
+ optout = true;
+
+ a = a && (flags & DNS_ANSWER_AUTHENTICATED);
+
+ no_wildcard = true;
+ }
+ }
+
+ if (wildcard_rr && no_wildcard)
+ return -EBADMSG;
+
+ if (!no_closer) {
+ *result = DNSSEC_NSEC_NO_RR;
+ return 0;
+ }
+
+ if (wildcard_rr) {
+ /* A wildcard exists that matches our query. */
+ if (optout)
+ /* This is not specified in any RFC to the best of my knowledge, but
+ * if the next closer enclosure is covered by an opt-out NSEC3 RR
+ * it means that we cannot prove that the source of synthesis is
+ * correct, as there may be a closer match. */
+ *result = DNSSEC_NSEC_OPTOUT;
+ else if (bitmap_isset(wildcard_rr->nsec3.types, key->type))
+ *result = DNSSEC_NSEC_FOUND;
+ else if (bitmap_isset(wildcard_rr->nsec3.types, DNS_TYPE_CNAME))
+ *result = DNSSEC_NSEC_CNAME;
+ else
+ *result = DNSSEC_NSEC_NODATA;
+ } else {
+ if (optout)
+ /* The RFC only specifies that we have to care for optout for NODATA for
+ * DS records. However, children of an insecure opt-out delegation should
+ * also be considered opt-out, rather than verified NXDOMAIN.
+ * Note that we do not require a proof of wildcard non-existence if the
+ * next closer domain is covered by an opt-out, as that would not provide
+ * any additional information. */
+ *result = DNSSEC_NSEC_OPTOUT;
+ else if (no_wildcard)
+ *result = DNSSEC_NSEC_NXDOMAIN;
+ else {
+ *result = DNSSEC_NSEC_NO_RR;
+
+ return 0;
+ }
+ }
+
+ if (authenticated)
+ *authenticated = a;
+
+ if (ttl)
+ *ttl = enclosure_rr->ttl;
+
+ return 0;
+}
+
+int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) {
+ DnsResourceRecord *rr;
+ bool have_nsec3 = false;
+ DnsAnswerFlags flags;
+ int r;
+
+ assert(key);
+ assert(result);
+
+ /* Look for any NSEC/NSEC3 RRs that say something about the specified key. */
+
+ DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
+
+ if (rr->key->class != key->class)
+ continue;
+
+ switch (rr->key->type) {
+
+ case DNS_TYPE_NSEC:
+
+ r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key));
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ if (bitmap_isset(rr->nsec.types, key->type))
+ *result = DNSSEC_NSEC_FOUND;
+ else if (bitmap_isset(rr->nsec.types, DNS_TYPE_CNAME))
+ *result = DNSSEC_NSEC_CNAME;
+ else
+ *result = DNSSEC_NSEC_NODATA;
+
+ if (authenticated)
+ *authenticated = flags & DNS_ANSWER_AUTHENTICATED;
+ if (ttl)
+ *ttl = rr->ttl;
+
+ return 0;
+ }
+
+ r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key), rr->nsec.next_domain_name);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ *result = DNSSEC_NSEC_NXDOMAIN;
+
+ if (authenticated)
+ *authenticated = flags & DNS_ANSWER_AUTHENTICATED;
+ if (ttl)
+ *ttl = rr->ttl;
+
+ return 0;
+ }
+ break;
+
+ case DNS_TYPE_NSEC3:
+ have_nsec3 = true;
+ break;
+ }
+ }
+
+ /* OK, this was not sufficient. Let's see if NSEC3 can help. */
+ if (have_nsec3)
+ return dnssec_test_nsec3(answer, key, result, authenticated, ttl);
+
+ /* No approproate NSEC RR found, report this. */
+ *result = DNSSEC_NSEC_NO_RR;
+ return 0;
+}
+
+int dnssec_nsec_test_between(DnsAnswer *answer, const char *name, const char *zone, bool *authenticated) {
+ DnsResourceRecord *rr;
+ DnsAnswerFlags flags;
+ int r;
+
+ assert(name);
+ assert(zone);
+
+ /* Checks whether there's an NSEC/NSEC3 that proves that the specified 'name' is non-existing in the specified
+ * 'zone'. The 'zone' must be a suffix of the 'name'. */
+
+ DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
+ bool found = false;
+
+ r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), zone);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ switch (rr->key->type) {
+
+ case DNS_TYPE_NSEC:
+ r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), name, rr->nsec.next_domain_name);
+ if (r < 0)
+ return r;
+
+ found = r > 0;
+ break;
+
+ case DNS_TYPE_NSEC3: {
+ _cleanup_free_ char *hashed_domain = NULL, *next_hashed_domain = NULL;
+
+ r = nsec3_is_good(rr, NULL);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ /* Format the domain we are testing with the NSEC3 RR's hash function */
+ r = nsec3_hashed_domain_make(
+ rr,
+ name,
+ zone,
+ &hashed_domain);
+ if (r < 0)
+ return r;
+ if ((size_t) r != rr->nsec3.next_hashed_name_size)
+ break;
+
+ /* Format the NSEC3's next hashed name as proper domain name */
+ r = nsec3_hashed_domain_format(
+ rr->nsec3.next_hashed_name,
+ rr->nsec3.next_hashed_name_size,
+ zone,
+ &next_hashed_domain);
+ if (r < 0)
+ return r;
+
+ r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), hashed_domain, next_hashed_domain);
+ if (r < 0)
+ return r;
+
+ found = r > 0;
+ break;
+ }
+
+ default:
+ continue;
+ }
+
+ if (found) {
+ if (authenticated)
+ *authenticated = flags & DNS_ANSWER_AUTHENTICATED;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
+ [DNSSEC_VALIDATED] = "validated",
+ [DNSSEC_VALIDATED_WILDCARD] = "validated-wildcard",
+ [DNSSEC_INVALID] = "invalid",
+ [DNSSEC_SIGNATURE_EXPIRED] = "signature-expired",
+ [DNSSEC_UNSUPPORTED_ALGORITHM] = "unsupported-algorithm",
+ [DNSSEC_NO_SIGNATURE] = "no-signature",
+ [DNSSEC_MISSING_KEY] = "missing-key",
+ [DNSSEC_UNSIGNED] = "unsigned",
+ [DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary",
+ [DNSSEC_NSEC_MISMATCH] = "nsec-mismatch",
+ [DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server",
};
-DEFINE_STRING_TABLE_LOOKUP(dnssec_mode, DnssecMode);
+DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult);
diff --git a/src/resolve/resolved-dns-dnssec.h b/src/resolve/resolved-dns-dnssec.h
index f4cb58988a..8a9bcf5b91 100644
--- a/src/resolve/resolved-dns-dnssec.h
+++ b/src/resolve/resolved-dns-dnssec.h
@@ -22,46 +22,68 @@
***/
typedef enum DnssecMode DnssecMode;
+typedef enum DnssecResult DnssecResult;
#include "dns-domain.h"
#include "resolved-dns-answer.h"
#include "resolved-dns-rr.h"
-enum DnssecMode {
- /* No DNSSEC validation is done */
- DNSSEC_NO,
-
- /* Trust the AD bit sent by the server. UNSAFE! */
- DNSSEC_TRUST,
-
- /* Validate locally, if the server knows DO, but if not, don't. Don't trust the AD bit */
- DNSSEC_YES,
-
- _DNSSEC_MODE_MAX,
- _DNSSEC_MODE_INVALID = -1
-};
-
-enum {
- DNSSEC_VERIFIED,
+enum DnssecResult {
+ /* These five are returned by dnssec_verify_rrset() */
+ DNSSEC_VALIDATED,
+ DNSSEC_VALIDATED_WILDCARD, /* Validated via a wildcard RRSIG, further NSEC/NSEC3 checks necessary */
DNSSEC_INVALID,
+ DNSSEC_SIGNATURE_EXPIRED,
+ DNSSEC_UNSUPPORTED_ALGORITHM,
+
+ /* These two are added by dnssec_verify_rrset_search() */
DNSSEC_NO_SIGNATURE,
DNSSEC_MISSING_KEY,
- DNSSEC_SIGNATURE_EXPIRED,
+
+ /* These two are added by the DnsTransaction logic */
+ DNSSEC_UNSIGNED,
+ DNSSEC_FAILED_AUXILIARY,
+ DNSSEC_NSEC_MISMATCH,
+ DNSSEC_INCOMPATIBLE_SERVER,
+
+ _DNSSEC_RESULT_MAX,
+ _DNSSEC_RESULT_INVALID = -1
};
#define DNSSEC_CANONICAL_HOSTNAME_MAX (DNS_HOSTNAME_MAX + 2)
-int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey);
-int dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig);
+/* The longest digest we'll ever generate, of all digest algorithms we support */
+#define DNSSEC_HASH_SIZE_MAX (MAX(20, 32))
+
+int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok);
+int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig);
+
+int dnssec_verify_rrset(DnsAnswer *answer, const DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime, DnssecResult *result);
+int dnssec_verify_rrset_search(DnsAnswer *answer, const DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime, DnssecResult *result, DnsResourceRecord **rrsig);
-int dnssec_verify_rrset(DnsAnswer *answer, DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime);
-int dnssec_verify_rrset_search(DnsAnswer *a, DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime);
+int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke);
+int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds);
-int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds);
+int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key);
-uint16_t dnssec_keytag(DnsResourceRecord *dnskey);
+uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke);
int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max);
-const char* dnssec_mode_to_string(DnssecMode m) _const_;
-DnssecMode dnssec_mode_from_string(const char *s) _pure_;
+int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret);
+
+typedef enum DnssecNsecResult {
+ DNSSEC_NSEC_NO_RR, /* No suitable NSEC/NSEC3 RR found */
+ DNSSEC_NSEC_CNAME, /* Didn't find what was asked for, but did find CNAME */
+ DNSSEC_NSEC_UNSUPPORTED_ALGORITHM,
+ DNSSEC_NSEC_NXDOMAIN,
+ DNSSEC_NSEC_NODATA,
+ DNSSEC_NSEC_FOUND,
+ DNSSEC_NSEC_OPTOUT,
+} DnssecNsecResult;
+
+int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl);
+int dnssec_nsec_test_between(DnsAnswer *answer, const char *name, const char *zone, bool *authenticated);
+
+const char* dnssec_result_to_string(DnssecResult m) _const_;
+DnssecResult dnssec_result_from_string(const char *s) _pure_;
diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c
index 9bd08eeec2..a8a8632491 100644
--- a/src/resolve/resolved-dns-packet.c
+++ b/src/resolve/resolved-dns-packet.c
@@ -58,6 +58,7 @@ int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) {
p->size = p->rindex = DNS_PACKET_HEADER_SIZE;
p->allocated = a;
p->protocol = protocol;
+ p->opt_start = p->opt_size = (size_t) -1;
p->n_ref = 1;
*ret = p;
@@ -65,20 +66,18 @@ int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) {
return 0;
}
-int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled) {
- DnsPacket *p;
- DnsPacketHeader *h;
- int r;
+void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated) {
- assert(ret);
+ DnsPacketHeader *h;
- r = dns_packet_new(&p, protocol, mtu);
- if (r < 0)
- return r;
+ assert(p);
h = DNS_PACKET_HEADER(p);
- if (protocol == DNS_PROTOCOL_LLMNR)
+ switch(p->protocol) {
+ case DNS_PROTOCOL_LLMNR:
+ assert(!truncated);
+
h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */,
0 /* opcode */,
0 /* c */,
@@ -88,17 +87,23 @@ int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool
0 /* ad */,
0 /* cd */,
0 /* rcode */));
- else if (protocol == DNS_PROTOCOL_MDNS)
- h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */,
- 0 /* opcode */,
- 0 /* aa */,
- 0 /* tc */,
- 0 /* rd (ask for recursion) */,
- 0 /* ra */,
- 0 /* ad */,
- 0 /* cd */,
- 0 /* rcode */));
- else
+ break;
+
+ case DNS_PROTOCOL_MDNS:
+ h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */,
+ 0 /* opcode */,
+ 0 /* aa */,
+ truncated /* tc */,
+ 0 /* rd (ask for recursion) */,
+ 0 /* ra */,
+ 0 /* ad */,
+ 0 /* cd */,
+ 0 /* rcode */));
+ break;
+
+ default:
+ assert(!truncated);
+
h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */,
0 /* opcode */,
0 /* aa */,
@@ -108,6 +113,23 @@ int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool
0 /* ad */,
dnssec_checking_disabled /* cd */,
0 /* rcode */));
+ }
+}
+
+int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled) {
+ DnsPacket *p;
+ int r;
+
+ assert(ret);
+
+ r = dns_packet_new(&p, protocol, mtu);
+ if (r < 0)
+ return r;
+
+ /* Always set the TC bit to 0 initially.
+ * If there are multiple packets later, we'll update the bit shortly before sending.
+ */
+ dns_packet_set_flags(p, dnssec_checking_disabled, false);
*ret = p;
return 0;
@@ -132,6 +154,7 @@ static void dns_packet_free(DnsPacket *p) {
dns_question_unref(p->question);
dns_answer_unref(p->answer);
+ dns_resource_record_unref(p->opt);
while ((s = hashmap_steal_first_key(p->names)))
free(s);
@@ -149,6 +172,8 @@ DnsPacket *dns_packet_unref(DnsPacket *p) {
assert(p->n_ref > 0);
+ dns_packet_unref(p->more);
+
if (p->n_ref == 1)
dns_packet_free(p);
else
@@ -185,6 +210,7 @@ int dns_packet_validate_reply(DnsPacket *p) {
return -EBADMSG;
switch (p->protocol) {
+
case DNS_PROTOCOL_LLMNR:
/* RFC 4795, Section 2.1.1. says to discard all replies with QDCOUNT != 1 */
if (DNS_PACKET_QDCOUNT(p) != 1)
@@ -225,6 +251,7 @@ int dns_packet_validate_query(DnsPacket *p) {
return -EBADMSG;
switch (p->protocol) {
+
case DNS_PROTOCOL_LLMNR:
/* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */
if (DNS_PACKET_QDCOUNT(p) != 1)
@@ -412,10 +439,15 @@ int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_
return 0;
}
-int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start) {
+int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, bool canonical_candidate, size_t *start) {
uint8_t *w;
int r;
+ /* Append a label to a packet. Optionally, does this in DNSSEC
+ * canonical form, if this label is marked as a candidate for
+ * it, and the canonical form logic is enabled for the
+ * packet */
+
assert(p);
assert(d);
@@ -428,18 +460,14 @@ int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start
*(w++) = (uint8_t) l;
- if (p->canonical_form) {
+ if (p->canonical_form && canonical_candidate) {
size_t i;
/* Generate in canonical form, as defined by DNSSEC
* RFC 4034, Section 6.2, i.e. all lower-case. */
- for (i = 0; i < l; i++) {
- if (d[i] >= 'A' && d[i] <= 'Z')
- w[i] = (uint8_t) (d[i] - 'A' + 'a');
- else
- w[i] = (uint8_t) d[i];
- }
+ for (i = 0; i < l; i++)
+ w[i] = (uint8_t) ascii_tolower(d[i]);
} else
/* Otherwise, just copy the string unaltered. This is
* essential for DNS-SD, where the casing of labels
@@ -453,6 +481,7 @@ int dns_packet_append_name(
DnsPacket *p,
const char *name,
bool allow_compression,
+ bool canonical_candidate,
size_t *start) {
size_t saved_size;
@@ -466,8 +495,8 @@ int dns_packet_append_name(
saved_size = p->size;
- while (*name) {
- _cleanup_free_ char *s = NULL;
+ while (!dns_name_is_root(name)) {
+ const char *z = name;
char label[DNS_LABEL_MAX];
size_t n = 0;
int k;
@@ -486,12 +515,6 @@ int dns_packet_append_name(
}
}
- s = strdup(name);
- if (!s) {
- r = -ENOMEM;
- goto fail;
- }
-
r = dns_label_unescape(&name, label, sizeof(label));
if (r < 0)
goto fail;
@@ -507,11 +530,19 @@ int dns_packet_append_name(
if (k > 0)
r = k;
- r = dns_packet_append_label(p, label, r, &n);
+ r = dns_packet_append_label(p, label, r, canonical_candidate, &n);
if (r < 0)
goto fail;
if (allow_compression) {
+ _cleanup_free_ char *s = NULL;
+
+ s = strdup(z);
+ if (!s) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
r = hashmap_ensure_allocated(&p->names, &dns_name_hash_ops);
if (r < 0)
goto fail;
@@ -548,7 +579,7 @@ int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start)
saved_size = p->size;
- r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), true, NULL);
+ r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), true, true, NULL);
if (r < 0)
goto fail;
@@ -570,7 +601,7 @@ fail:
return r;
}
-static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t length, uint8_t *types, size_t *start) {
+static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t length, const uint8_t *types, size_t *start) {
size_t saved_size;
int r;
@@ -611,7 +642,6 @@ static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) {
int r;
assert(p);
- assert(types);
saved_size = p->size;
@@ -627,15 +657,16 @@ static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) {
}
window = n >> 8;
-
entry = n & 255;
bitmaps[entry / 8] |= 1 << (7 - (entry % 8));
}
- r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL);
- if (r < 0)
- goto fail;
+ if (bitmaps[entry / 8] != 0) {
+ r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL);
+ if (r < 0)
+ goto fail;
+ }
if (start)
*start = saved_size;
@@ -647,7 +678,7 @@ fail:
}
/* Append the OPT pseudo-RR described in RFC6891 */
-int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start) {
+int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start) {
size_t saved_size;
int r;
@@ -655,6 +686,11 @@ int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do,
/* we must never advertise supported packet size smaller than the legacy max */
assert(max_udp_size >= DNS_PACKET_UNICAST_SIZE_MAX);
+ if (p->opt_start != (size_t) -1)
+ return -EBUSY;
+
+ assert(p->opt_size == (size_t) -1);
+
saved_size = p->size;
/* empty name */
@@ -683,10 +719,48 @@ int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do,
goto fail;
/* RDLENGTH */
- r = dns_packet_append_uint16(p, 0, NULL);
+
+ if (edns0_do) {
+ /* If DO is on, also append RFC6975 Algorithm data */
+
+ static const uint8_t rfc6975[] = {
+
+ 0, 5, /* OPTION_CODE: DAU */
+ 0, 6, /* LIST_LENGTH */
+ DNSSEC_ALGORITHM_RSASHA1,
+ DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1,
+ DNSSEC_ALGORITHM_RSASHA256,
+ DNSSEC_ALGORITHM_RSASHA512,
+ DNSSEC_ALGORITHM_ECDSAP256SHA256,
+ DNSSEC_ALGORITHM_ECDSAP384SHA384,
+
+ 0, 6, /* OPTION_CODE: DHU */
+ 0, 3, /* LIST_LENGTH */
+ DNSSEC_DIGEST_SHA1,
+ DNSSEC_DIGEST_SHA256,
+ DNSSEC_DIGEST_SHA384,
+
+ 0, 7, /* OPTION_CODE: N3U */
+ 0, 1, /* LIST_LENGTH */
+ NSEC3_ALGORITHM_SHA1,
+ };
+
+ r = dns_packet_append_uint16(p, sizeof(rfc6975), NULL);
+ if (r < 0)
+ goto fail;
+
+ r = dns_packet_append_blob(p, rfc6975, sizeof(rfc6975), NULL);
+ } else
+ r = dns_packet_append_uint16(p, 0, NULL);
+
if (r < 0)
goto fail;
+ DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) + 1);
+
+ p->opt_start = saved_size;
+ p->opt_size = p->size - saved_size;
+
if (start)
*start = saved_size;
@@ -697,6 +771,27 @@ fail:
return r;
}
+int dns_packet_truncate_opt(DnsPacket *p) {
+ assert(p);
+
+ if (p->opt_start == (size_t) -1) {
+ assert(p->opt_size == (size_t) -1);
+ return 0;
+ }
+
+ assert(p->opt_size != (size_t) -1);
+ assert(DNS_PACKET_ARCOUNT(p) > 0);
+
+ if (p->opt_start + p->opt_size != p->size)
+ return -EBUSY;
+
+ dns_packet_truncate(p, p->opt_start);
+ DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) - 1);
+ p->opt_start = p->opt_size = (size_t) -1;
+
+ return 1;
+}
+
int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start) {
size_t saved_size, rdlength_offset, end, rdlength, rds;
int r;
@@ -736,14 +831,14 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
if (r < 0)
goto fail;
- r = dns_packet_append_name(p, rr->srv.name, true, NULL);
+ r = dns_packet_append_name(p, rr->srv.name, true, false, NULL);
break;
case DNS_TYPE_PTR:
case DNS_TYPE_NS:
case DNS_TYPE_CNAME:
case DNS_TYPE_DNAME:
- r = dns_packet_append_name(p, rr->ptr.name, true, NULL);
+ r = dns_packet_append_name(p, rr->ptr.name, true, false, NULL);
break;
case DNS_TYPE_HINFO:
@@ -786,11 +881,11 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
break;
case DNS_TYPE_SOA:
- r = dns_packet_append_name(p, rr->soa.mname, true, NULL);
+ r = dns_packet_append_name(p, rr->soa.mname, true, false, NULL);
if (r < 0)
goto fail;
- r = dns_packet_append_name(p, rr->soa.rname, true, NULL);
+ r = dns_packet_append_name(p, rr->soa.rname, true, false, NULL);
if (r < 0)
goto fail;
@@ -818,7 +913,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
if (r < 0)
goto fail;
- r = dns_packet_append_name(p, rr->mx.exchange, true, NULL);
+ r = dns_packet_append_name(p, rr->mx.exchange, true, false, NULL);
break;
case DNS_TYPE_LOC:
@@ -922,7 +1017,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
if (r < 0)
goto fail;
- r = dns_packet_append_name(p, rr->rrsig.signer, false, NULL);
+ r = dns_packet_append_name(p, rr->rrsig.signer, false, true, NULL);
if (r < 0)
goto fail;
@@ -930,7 +1025,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
break;
case DNS_TYPE_NSEC:
- r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, NULL);
+ r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, false, NULL);
if (r < 0)
goto fail;
@@ -939,6 +1034,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
goto fail;
break;
+
case DNS_TYPE_NSEC3:
r = dns_packet_append_uint8(p, rr->nsec3.algorithm, NULL);
if (r < 0)
@@ -973,6 +1069,8 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
goto fail;
break;
+
+ case DNS_TYPE_OPT:
case _DNS_TYPE_INVALID: /* unparseable */
default:
@@ -1009,7 +1107,6 @@ fail:
return r;
}
-
int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start) {
assert(p);
@@ -1419,7 +1516,7 @@ fail:
return r;
}
-int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) {
+int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start) {
_cleanup_free_ char *name = NULL;
bool cache_flush = false;
uint16_t class, type;
@@ -1447,7 +1544,7 @@ int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) {
if (p->protocol == DNS_PROTOCOL_MDNS) {
/* See RFC6762, Section 10.2 */
- if (class & MDNS_RR_CACHE_FLUSH) {
+ if (type != DNS_TYPE_OPT && (class & MDNS_RR_CACHE_FLUSH)) {
class &= ~MDNS_RR_CACHE_FLUSH;
cache_flush = true;
}
@@ -1459,11 +1556,11 @@ int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) {
goto fail;
}
- key->cache_flush = cache_flush;
-
name = NULL;
*ret = key;
+ if (ret_cache_flush)
+ *ret_cache_flush = cache_flush;
if (start)
*start = saved_rindex;
@@ -1479,11 +1576,12 @@ static bool loc_size_ok(uint8_t size) {
return m <= 9 && e <= 9 && (m > 0 || e == 0);
}
-int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
+int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_flush, size_t *start) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
size_t saved_rindex, offset;
uint16_t rdlength;
+ bool cache_flush;
int r;
assert(p);
@@ -1491,12 +1589,12 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
saved_rindex = p->rindex;
- r = dns_packet_read_key(p, &key, NULL);
+ r = dns_packet_read_key(p, &key, &cache_flush, NULL);
if (r < 0)
goto fail;
- if (key->class == DNS_CLASS_ANY ||
- key->type == DNS_TYPE_ANY) {
+ if (!dns_class_is_valid_rr(key->class)||
+ !dns_type_is_valid_rr(key->type)) {
r = -EBADMSG;
goto fail;
}
@@ -1511,6 +1609,11 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
if (r < 0)
goto fail;
+ /* RFC 2181, Section 8, suggests to
+ * treat a TTL with the MSB set as a zero TTL. */
+ if (rr->ttl & UINT32_C(0x80000000))
+ rr->ttl = 0;
+
r = dns_packet_read_uint16(p, &rdlength, NULL);
if (r < 0)
goto fail;
@@ -1544,10 +1647,6 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
r = dns_packet_read_name(p, &rr->ptr.name, true, NULL);
break;
- case DNS_TYPE_OPT: /* we only care about the header */
- r = 0;
- break;
-
case DNS_TYPE_HINFO:
r = dns_packet_read_string(p, &rr->hinfo.cpu, NULL);
if (r < 0)
@@ -1725,6 +1824,7 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
}
break;
+
case DNS_TYPE_SSHFP:
r = dns_packet_read_uint8(p, &rr->sshfp.algorithm, NULL);
if (r < 0)
@@ -1822,7 +1922,7 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
case DNS_TYPE_NSEC: {
/*
- * RFC6762, section 18.14 explicly states mDNS should use name compression.
+ * RFC6762, section 18.14 explictly states mDNS should use name compression.
* This contradicts RFC3845, section 2.1.1
*/
@@ -1887,6 +1987,8 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
break;
}
+
+ case DNS_TYPE_OPT: /* we only care about the header of OPT for now. */
default:
unparseable:
r = dns_packet_read_memdup(p, rdlength, &rr->generic.data, &rr->generic.size, NULL);
@@ -1904,6 +2006,8 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
*ret = rr;
rr = NULL;
+ if (ret_cache_flush)
+ *ret_cache_flush = cache_flush;
if (start)
*start = saved_rindex;
@@ -1936,11 +2040,22 @@ int dns_packet_extract(DnsPacket *p) {
for (i = 0; i < n; i++) {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
+ bool cache_flush;
- r = dns_packet_read_key(p, &key, NULL);
+ r = dns_packet_read_key(p, &key, &cache_flush, NULL);
if (r < 0)
goto finish;
+ if (cache_flush) {
+ r = -EBADMSG;
+ goto finish;
+ }
+
+ if (!dns_type_is_valid_query(key->type)) {
+ r = -EBADMSG;
+ goto finish;
+ }
+
r = dns_question_add(question, key);
if (r < 0)
goto finish;
@@ -1957,14 +2072,49 @@ int dns_packet_extract(DnsPacket *p) {
for (i = 0; i < n; i++) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
+ bool cache_flush;
- r = dns_packet_read_rr(p, &rr, NULL);
+ r = dns_packet_read_rr(p, &rr, &cache_flush, NULL);
if (r < 0)
goto finish;
- r = dns_answer_add(answer, rr, p->ifindex);
- if (r < 0)
- goto finish;
+ if (rr->key->type == DNS_TYPE_OPT) {
+
+ if (!dns_name_is_root(DNS_RESOURCE_KEY_NAME(rr->key))) {
+ r = -EBADMSG;
+ goto finish;
+ }
+
+ /* Note that we accept the OPT RR in
+ * any section, not just in the
+ * additional section, as some routers
+ * (Belkin!) blindly copy the OPT RR
+ * from the query to the reply packet,
+ * and don't get the section right. */
+
+ /* Two OPT RRs? */
+ if (p->opt) {
+ r = -EBADMSG;
+ goto finish;
+ }
+
+ p->opt = dns_resource_record_ref(rr);
+ } else {
+
+ /* According to RFC 4795, section
+ * 2.9. only the RRs from the Answer
+ * section shall be cached. Hence mark
+ * only those RRs as cacheable by
+ * default, but not the ones from the
+ * Additional or Authority
+ * sections. */
+
+ r = dns_answer_add(answer, rr, p->ifindex,
+ (i < DNS_PACKET_ANCOUNT(p) ? DNS_ANSWER_CACHEABLE : 0) |
+ (p->protocol == DNS_PROTOCOL_MDNS && !cache_flush ? DNS_ANSWER_SHARED_OWNER : 0));
+ if (r < 0)
+ goto finish;
+ }
}
}
@@ -1983,6 +2133,30 @@ finish:
return r;
}
+int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key) {
+ int r;
+
+ assert(p);
+ assert(key);
+
+ /* Checks if the specified packet is a reply for the specified
+ * key and the specified key is the only one in the question
+ * section. */
+
+ if (DNS_PACKET_QR(p) != 1)
+ return 0;
+
+ /* Let's unpack the packet, if that hasn't happened yet. */
+ r = dns_packet_extract(p);
+ if (r < 0)
+ return r;
+
+ if (p->question->n_keys != 1)
+ return 0;
+
+ return dns_resource_key_equal(p->question->keys[0], key);
+}
+
static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = {
[DNS_RCODE_SUCCESS] = "SUCCESS",
[DNS_RCODE_FORMERR] = "FORMERR",
diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h
index 48b3572cb4..6821be73e4 100644
--- a/src/resolve/resolved-dns-packet.h
+++ b/src/resolve/resolved-dns-packet.h
@@ -76,10 +76,12 @@ struct DnsPacket {
size_t size, allocated, rindex;
void *_data; /* don't access directly, use DNS_PACKET_DATA()! */
Hashmap *names; /* For name compression */
+ size_t opt_start, opt_size;
/* Parsed data */
DnsQuestion *question;
DnsAnswer *answer;
+ DnsResourceRecord *opt;
/* Packet reception metadata */
int ifindex;
@@ -88,6 +90,9 @@ struct DnsPacket {
uint16_t sender_port, destination_port;
uint32_t ttl;
+ /* For support of truncated packets */
+ DnsPacket *more;
+
bool on_stack:1;
bool extracted:1;
bool refuse_compression:1;
@@ -114,7 +119,17 @@ static inline uint8_t* DNS_PACKET_DATA(DnsPacket *p) {
#define DNS_PACKET_RA(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 7) & 1)
#define DNS_PACKET_AD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 5) & 1)
#define DNS_PACKET_CD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 4) & 1)
-#define DNS_PACKET_RCODE(p) (be16toh(DNS_PACKET_HEADER(p)->flags) & 15)
+
+static inline uint16_t DNS_PACKET_RCODE(DnsPacket *p) {
+ uint16_t rcode;
+
+ if (p->opt)
+ rcode = (uint16_t) (p->opt->ttl >> 24);
+ else
+ rcode = 0;
+
+ return rcode | (be16toh(DNS_PACKET_HEADER(p)->flags) & 15);
+}
/* LLMNR defines some bits differently */
#define DNS_PACKET_LLMNR_C(p) DNS_PACKET_AA(p)
@@ -146,6 +161,8 @@ static inline unsigned DNS_PACKET_RRCOUNT(DnsPacket *p) {
int dns_packet_new(DnsPacket **p, DnsProtocol protocol, size_t mtu);
int dns_packet_new_query(DnsPacket **p, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled);
+void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated);
+
DnsPacket *dns_packet_ref(DnsPacket *p);
DnsPacket *dns_packet_unref(DnsPacket *p);
@@ -155,19 +172,22 @@ int dns_packet_validate(DnsPacket *p);
int dns_packet_validate_reply(DnsPacket *p);
int dns_packet_validate_query(DnsPacket *p);
+int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key);
+
int dns_packet_append_blob(DnsPacket *p, const void *d, size_t sz, size_t *start);
int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start);
int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start);
int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start);
int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start);
int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, 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, bool allow_compression, size_t *start);
+int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, bool canonical_candidate, size_t *start);
+int dns_packet_append_name(DnsPacket *p, const char *name, bool allow_compression, bool canonical_candidate, size_t *start);
int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *key, size_t *start);
int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start);
-int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start);
+int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start);
void dns_packet_truncate(DnsPacket *p, size_t sz);
+int dns_packet_truncate_opt(DnsPacket *p);
int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start);
int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start);
@@ -177,8 +197,8 @@ 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_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start);
int dns_packet_read_name(DnsPacket *p, char **ret, bool allow_compression, 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_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start);
+int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_flush, size_t *start);
void dns_packet_rewind(DnsPacket *p, size_t idx);
@@ -193,6 +213,7 @@ static inline bool DNS_PACKET_SHALL_CACHE(DnsPacket *p) {
return in_addr_is_localhost(p->family, &p->sender) == 0;
}
+/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 */
enum {
DNS_RCODE_SUCCESS = 0,
DNS_RCODE_FORMERR = 1,
diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c
index 089d9fb70d..1948d59fc4 100644
--- a/src/resolve/resolved-dns-query.c
+++ b/src/resolve/resolved-dns-query.c
@@ -59,7 +59,7 @@ static void dns_query_candidate_stop(DnsQueryCandidate *c) {
assert(c);
while ((t = set_steal_first(c->transactions))) {
- set_remove(t->query_candidates, c);
+ set_remove(t->notify_query_candidates, c);
dns_transaction_gc(t);
}
}
@@ -116,32 +116,35 @@ static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResource
assert(c);
assert(key);
- r = set_ensure_allocated(&c->transactions, NULL);
- if (r < 0)
- return r;
-
t = dns_scope_find_transaction(c->scope, key, true);
if (!t) {
r = dns_transaction_new(&t, c->scope, key);
if (r < 0)
return r;
+ } else {
+ if (set_contains(c->transactions, t))
+ return 0;
}
- r = set_ensure_allocated(&t->query_candidates, NULL);
+ r = set_ensure_allocated(&c->transactions, NULL);
+ if (r < 0)
+ goto gc;
+
+ r = set_ensure_allocated(&t->notify_query_candidates, NULL);
if (r < 0)
goto gc;
- r = set_put(t->query_candidates, c);
+ r = set_put(t->notify_query_candidates, c);
if (r < 0)
goto gc;
r = set_put(c->transactions, t);
if (r < 0) {
- set_remove(t->query_candidates, c);
+ (void) set_remove(t->notify_query_candidates, c);
goto gc;
}
- return 0;
+ return 1;
gc:
dns_transaction_gc(t);
@@ -182,9 +185,21 @@ static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) {
switch (t->state) {
- case DNS_TRANSACTION_PENDING:
case DNS_TRANSACTION_NULL:
- return t->state;
+ /* If there's a NULL transaction pending, then
+ * this means not all transactions where
+ * started yet, and we were called from within
+ * the stackframe that is supposed to start
+ * remaining transactions. In this case,
+ * simply claim the candidate is pending. */
+
+ case DNS_TRANSACTION_PENDING:
+ case DNS_TRANSACTION_VALIDATING:
+ /* If there's one transaction currently in
+ * VALIDATING state, then this means there's
+ * also one in PENDING state, hence we can
+ * return PENDING immediately. */
+ return DNS_TRANSACTION_PENDING;
case DNS_TRANSACTION_SUCCESS:
state = t->state;
@@ -233,7 +248,7 @@ fail:
return r;
}
-void dns_query_candidate_ready(DnsQueryCandidate *c) {
+void dns_query_candidate_notify(DnsQueryCandidate *c) {
DnsTransactionState state;
int r;
@@ -241,7 +256,7 @@ void dns_query_candidate_ready(DnsQueryCandidate *c) {
state = dns_query_candidate_state(c);
- if (IN_SET(state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_NULL))
+ if (DNS_TRANSACTION_IS_LIVE(state))
return;
if (state != DNS_TRANSACTION_SUCCESS && c->search_domain) {
@@ -394,8 +409,8 @@ int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for) {
static void dns_query_complete(DnsQuery *q, DnsTransactionState state) {
assert(q);
- assert(!IN_SET(state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING));
- assert(IN_SET(q->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING));
+ assert(!DNS_TRANSACTION_IS_LIVE(state));
+ assert(DNS_TRANSACTION_IS_LIVE(q->state));
/* Note that this call might invalidate the query. Callers
* should hence not attempt to access the query or transaction
@@ -539,7 +554,7 @@ static int synthesize_localhost_rr(DnsQuery *q, DnsResourceKey *key, DnsAnswer *
rr->a.in_addr.s_addr = htobe32(INADDR_LOOPBACK);
- r = dns_answer_add(*answer, rr, SYNTHESIZE_IFINDEX(q->ifindex));
+ r = dns_answer_add(*answer, rr, SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED);
if (r < 0)
return r;
}
@@ -553,7 +568,7 @@ static int synthesize_localhost_rr(DnsQuery *q, DnsResourceKey *key, DnsAnswer *
rr->aaaa.in6_addr = in6addr_loopback;
- r = dns_answer_add(*answer, rr, SYNTHESIZE_IFINDEX(q->ifindex));
+ r = dns_answer_add(*answer, rr, SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED);
if (r < 0)
return r;
}
@@ -561,7 +576,7 @@ static int synthesize_localhost_rr(DnsQuery *q, DnsResourceKey *key, DnsAnswer *
return 0;
}
-static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to, int ifindex) {
+static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to, int ifindex, DnsAnswerFlags flags) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, from);
@@ -572,7 +587,7 @@ static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to,
if (!rr->ptr.name)
return -ENOMEM;
- return dns_answer_add(*answer, rr, ifindex);
+ return dns_answer_add(*answer, rr, ifindex, flags);
}
static int synthesize_localhost_ptr(DnsQuery *q, DnsResourceKey *key, DnsAnswer **answer) {
@@ -582,12 +597,12 @@ static int synthesize_localhost_ptr(DnsQuery *q, DnsResourceKey *key, DnsAnswer
assert(key);
assert(answer);
- r = dns_answer_reserve(answer, 1);
- if (r < 0)
- return r;
-
if (IN_SET(key->type, DNS_TYPE_PTR, DNS_TYPE_ANY)) {
- r = answer_add_ptr(answer, DNS_RESOURCE_KEY_NAME(key), "localhost", SYNTHESIZE_IFINDEX(q->ifindex));
+ r = dns_answer_reserve(answer, 1);
+ if (r < 0)
+ return r;
+
+ r = answer_add_ptr(answer, DNS_RESOURCE_KEY_NAME(key), "localhost", SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED);
if (r < 0)
return r;
}
@@ -618,7 +633,7 @@ static int answer_add_addresses_rr(
if (r < 0)
return r;
- r = dns_answer_add(*answer, rr, addresses[j].ifindex);
+ r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED);
if (r < 0)
return r;
}
@@ -659,7 +674,7 @@ static int answer_add_addresses_ptr(
if (r < 0)
return r;
- r = dns_answer_add(*answer, rr, addresses[j].ifindex);
+ r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED);
if (r < 0)
return r;
}
@@ -725,15 +740,15 @@ static int synthesize_system_hostname_ptr(DnsQuery *q, int af, const union in_ad
if (r < 0)
return r;
- r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", q->manager->llmnr_hostname, SYNTHESIZE_IFINDEX(q->ifindex));
+ r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", q->manager->llmnr_hostname, SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED);
if (r < 0)
return r;
- r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", q->manager->mdns_hostname, SYNTHESIZE_IFINDEX(q->ifindex));
+ r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", q->manager->mdns_hostname, SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED);
if (r < 0)
return r;
- r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", "localhost", SYNTHESIZE_IFINDEX(q->ifindex));
+ r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", "localhost", SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED);
if (r < 0)
return r;
@@ -795,7 +810,7 @@ static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) {
/* Tries to synthesize localhost RR replies where appropriate */
if (!IN_SET(*state,
- DNS_TRANSACTION_FAILURE,
+ DNS_TRANSACTION_RCODE_FAILURE,
DNS_TRANSACTION_NO_SERVERS,
DNS_TRANSACTION_TIMEOUT,
DNS_TRANSACTION_ATTEMPTS_MAX_REACHED))
@@ -946,6 +961,8 @@ int dns_query_go(DnsQuery *q) {
if (r < 0)
goto fail;
+ (void) sd_event_source_set_description(q->timeout_event_source, "query-timeout");
+
q->state = DNS_TRANSACTION_PENDING;
q->block_ready++;
@@ -970,9 +987,11 @@ fail:
static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS;
+ bool has_authenticated = false, has_non_authenticated = false;
+ DnssecResult dnssec_result_authenticated = _DNSSEC_RESULT_INVALID, dnssec_result_non_authenticated = _DNSSEC_RESULT_INVALID;
DnsTransaction *t;
Iterator i;
- bool has_authenticated = false, has_non_authenticated = false;
+ int r;
assert(q);
@@ -988,29 +1007,29 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
case DNS_TRANSACTION_SUCCESS: {
/* We found a successfuly reply, merge it into the answer */
- DnsAnswer *merged;
-
- merged = dns_answer_merge(q->answer, t->answer);
- if (!merged) {
+ r = dns_answer_extend(&q->answer, t->answer);
+ if (r < 0) {
dns_query_complete(q, DNS_TRANSACTION_RESOURCES);
return;
}
- dns_answer_unref(q->answer);
- q->answer = merged;
q->answer_rcode = t->answer_rcode;
- if (t->answer_authenticated)
+ if (t->answer_authenticated) {
has_authenticated = true;
- else
+ dnssec_result_authenticated = t->answer_dnssec_result;
+ } else {
has_non_authenticated = true;
+ dnssec_result_non_authenticated = t->answer_dnssec_result;
+ }
state = DNS_TRANSACTION_SUCCESS;
break;
}
- case DNS_TRANSACTION_PENDING:
case DNS_TRANSACTION_NULL:
+ case DNS_TRANSACTION_PENDING:
+ case DNS_TRANSACTION_VALIDATING:
case DNS_TRANSACTION_ABORTED:
/* Ignore transactions that didn't complete */
continue;
@@ -1019,22 +1038,25 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) {
/* Any kind of failure? Store the data away,
* if there's nothing stored yet. */
- if (state != DNS_TRANSACTION_SUCCESS) {
-
- dns_answer_unref(q->answer);
- q->answer = dns_answer_ref(t->answer);
- q->answer_rcode = t->answer_rcode;
+ if (state == DNS_TRANSACTION_SUCCESS)
+ continue;
- state = t->state;
- }
+ q->answer = dns_answer_unref(q->answer);
+ q->answer_rcode = t->answer_rcode;
+ q->answer_dnssec_result = t->answer_dnssec_result;
+ state = t->state;
break;
}
}
+ if (state == DNS_TRANSACTION_SUCCESS) {
+ q->answer_authenticated = has_authenticated && !has_non_authenticated;
+ q->answer_dnssec_result = q->answer_authenticated ? dnssec_result_authenticated : dnssec_result_non_authenticated;
+ }
+
q->answer_protocol = c->scope->protocol;
q->answer_family = c->scope->family;
- q->answer_authenticated = has_authenticated && !has_non_authenticated;
dns_search_domain_unref(q->answer_search_domain);
q->answer_search_domain = dns_search_domain_ref(c->search_domain);
@@ -1049,7 +1071,7 @@ void dns_query_ready(DnsQuery *q) {
bool pending = false;
assert(q);
- assert(IN_SET(q->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING));
+ assert(DNS_TRANSACTION_IS_LIVE(q->state));
/* Note that this call might invalidate the query. Callers
* should hence not attempt to access the query or transaction
@@ -1066,14 +1088,16 @@ void dns_query_ready(DnsQuery *q) {
switch (state) {
case DNS_TRANSACTION_SUCCESS:
- /* One of the transactions is successful,
+ /* One of the candidates is successful,
* let's use it, and copy its data out */
dns_query_accept(q, c);
return;
- case DNS_TRANSACTION_PENDING:
case DNS_TRANSACTION_NULL:
- /* One of the transactions is still going on, let's maybe wait for it */
+ case DNS_TRANSACTION_PENDING:
+ case DNS_TRANSACTION_VALIDATING:
+ /* One of the candidates is still going on,
+ * let's maybe wait for it */
pending = true;
break;
@@ -1096,6 +1120,8 @@ static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname)
assert(q);
+ log_debug("Following CNAME %s → %s", dns_question_first_name(q->question), cname->cname.name);
+
q->n_cname_redirects ++;
if (q->n_cname_redirects > CNAME_MAX)
return -ELOOP;
@@ -1121,8 +1147,8 @@ int dns_query_process_cname(DnsQuery *q) {
assert(q);
- if (q->state != DNS_TRANSACTION_SUCCESS)
- return 0;
+ if (!IN_SET(q->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_NULL))
+ return DNS_QUERY_NOMATCH;
DNS_ANSWER_FOREACH(rr, q->answer) {
@@ -1130,7 +1156,7 @@ int dns_query_process_cname(DnsQuery *q) {
if (r < 0)
return r;
if (r > 0)
- return 0; /* The answer matches directly, no need to follow cnames */
+ return DNS_QUERY_MATCH; /* The answer matches directly, no need to follow cnames */
r = dns_question_matches_cname(q->question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain));
if (r < 0)
@@ -1140,7 +1166,7 @@ int dns_query_process_cname(DnsQuery *q) {
}
if (!cname)
- return 0; /* No cname to follow */
+ return DNS_QUERY_NOMATCH; /* No match and no cname to follow */
if (q->flags & SD_RESOLVED_NO_CNAME)
return -ELOOP;
@@ -1152,20 +1178,16 @@ int dns_query_process_cname(DnsQuery *q) {
/* Let's see if the answer can already answer the new
* redirected question */
- DNS_ANSWER_FOREACH(rr, q->answer) {
- r = dns_question_matches_rr(q->question, rr, NULL);
- if (r < 0)
- return r;
- if (r > 0)
- return 0; /* It can answer it, yay! */
- }
+ r = dns_query_process_cname(q);
+ if (r != DNS_QUERY_NOMATCH)
+ return r;
/* OK, it cannot, let's begin with the new query */
r = dns_query_go(q);
if (r < 0)
return r;
- return 1; /* We return > 0, if we restarted the query for a new cname */
+ return DNS_QUERY_RESTARTED; /* We restarted the query for a new cname */
}
static int on_bus_track(sd_bus_track *t, void *userdata) {
diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h
index b71bb2352b..4a0d265a2d 100644
--- a/src/resolve/resolved-dns-query.h
+++ b/src/resolve/resolved-dns-query.h
@@ -72,10 +72,11 @@ struct DnsQuery {
/* Discovered data */
DnsAnswer *answer;
int answer_rcode;
+ DnssecResult answer_dnssec_result;
+ bool answer_authenticated;
DnsProtocol answer_protocol;
int answer_family;
DnsSearchDomain *answer_search_domain;
- bool answer_authenticated;
/* Bus client information */
sd_bus_message *request;
@@ -94,8 +95,14 @@ struct DnsQuery {
LIST_FIELDS(DnsQuery, auxiliary_queries);
};
+enum {
+ DNS_QUERY_MATCH,
+ DNS_QUERY_NOMATCH,
+ DNS_QUERY_RESTARTED,
+};
+
DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c);
-void dns_query_candidate_ready(DnsQueryCandidate *c);
+void dns_query_candidate_notify(DnsQueryCandidate *c);
int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question, int family, uint64_t flags);
DnsQuery *dns_query_free(DnsQuery *q);
diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c
index 3249448d3b..4ed7434d3c 100644
--- a/src/resolve/resolved-dns-question.c
+++ b/src/resolve/resolved-dns-question.c
@@ -117,7 +117,7 @@ int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr, const char
return 0;
for (i = 0; i < q->n_keys; i++) {
- r = dns_resource_key_match_cname(q->keys[i], rr, search_domain);
+ r = dns_resource_key_match_cname_or_dname(q->keys[i], rr->key, search_domain);
if (r != 0)
return r;
}
diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c
index 934a18334c..dbf840157f 100644
--- a/src/resolve/resolved-dns-rr.c
+++ b/src/resolve/resolved-dns-rr.c
@@ -168,6 +168,9 @@ bool dns_resource_key_is_address(const DnsResourceKey *key) {
int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) {
int r;
+ if (a == b)
+ return 1;
+
r = dns_name_equal(DNS_RESOURCE_KEY_NAME(a), DNS_RESOURCE_KEY_NAME(b));
if (r <= 0)
return r;
@@ -181,12 +184,15 @@ int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) {
return 1;
}
-int dns_resource_key_match_rr(const DnsResourceKey *key, const DnsResourceRecord *rr, const char *search_domain) {
+int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain) {
int r;
assert(key);
assert(rr);
+ if (key == rr->key)
+ return 1;
+
/* Checks if an rr matches the specified key. If a search
* domain is specified, it will also be checked if the key
* with the search domain suffixed might match the RR. */
@@ -214,19 +220,19 @@ int dns_resource_key_match_rr(const DnsResourceKey *key, const DnsResourceRecord
return 0;
}
-int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRecord *rr, const char *search_domain) {
+int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain) {
int r;
assert(key);
- assert(rr);
+ assert(cname);
- if (rr->key->class != key->class && key->class != DNS_CLASS_ANY)
+ if (cname->class != key->class && key->class != DNS_CLASS_ANY)
return 0;
- if (rr->key->type == DNS_TYPE_CNAME)
- r = dns_name_equal(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(rr->key));
- else if (rr->key->type == DNS_TYPE_DNAME)
- r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(rr->key));
+ if (cname->type == DNS_TYPE_CNAME)
+ r = dns_name_equal(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname));
+ else if (cname->type == DNS_TYPE_DNAME)
+ r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname));
else
return 0;
@@ -240,14 +246,28 @@ int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRec
if (r < 0)
return r;
- if (rr->key->type == DNS_TYPE_CNAME)
- return dns_name_equal(joined, DNS_RESOURCE_KEY_NAME(rr->key));
- else if (rr->key->type == DNS_TYPE_DNAME)
- return dns_name_endswith(joined, DNS_RESOURCE_KEY_NAME(rr->key));
+ if (cname->type == DNS_TYPE_CNAME)
+ return dns_name_equal(joined, DNS_RESOURCE_KEY_NAME(cname));
+ else if (cname->type == DNS_TYPE_DNAME)
+ return dns_name_endswith(joined, DNS_RESOURCE_KEY_NAME(cname));
}
return 0;
+}
+
+int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *soa) {
+ assert(soa);
+ assert(key);
+ /* Checks whether 'soa' is a SOA record for the specified key. */
+
+ if (soa->class != key->class)
+ return 0;
+
+ if (soa->type != DNS_TYPE_SOA)
+ return 0;
+
+ return dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(soa));
}
static void dns_resource_key_hash_func(const void *i, struct siphash *state) {
@@ -288,9 +308,12 @@ const struct hash_ops dns_resource_key_hash_ops = {
int dns_resource_key_to_string(const DnsResourceKey *key, char **ret) {
char cbuf[strlen("CLASS") + DECIMAL_STR_MAX(uint16_t)], tbuf[strlen("TYPE") + DECIMAL_STR_MAX(uint16_t)];
- const char *c, *t;
+ const char *c, *t, *n;
char *s;
+ /* If we cannot convert the CLASS/TYPE into a known string,
+ use the format recommended by RFC 3597, Section 5. */
+
c = dns_class_to_string(key->class);
if (!c) {
sprintf(cbuf, "CLASS%u", key->class);
@@ -303,7 +326,8 @@ int dns_resource_key_to_string(const DnsResourceKey *key, char **ret) {
t = tbuf;
}
- if (asprintf(&s, "%s %s %-5s", DNS_RESOURCE_KEY_NAME(key), c, t) < 0)
+ n = DNS_RESOURCE_KEY_NAME(key);
+ if (asprintf(&s, "%s%s %s %-5s", n, endswith(n, ".") ? "" : ".", c, t) < 0)
return -ENOMEM;
*ret = s;
@@ -319,6 +343,7 @@ DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key) {
rr->n_ref = 1;
rr->key = dns_resource_key_ref(key);
+ rr->expiry = USEC_INFINITY;
return rr;
}
@@ -428,6 +453,7 @@ DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr) {
dns_resource_key_unref(rr->key);
}
+ free(rr->to_string);
free(rr);
return NULL;
@@ -503,6 +529,9 @@ int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecor
assert(a);
assert(b);
+ if (a == b)
+ return 1;
+
r = dns_resource_key_equal(a->key, b->key);
if (r <= 0)
return r;
@@ -740,16 +769,19 @@ static char *format_txt(DnsTxtItem *first) {
return s;
}
-int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
+const char *dns_resource_record_to_string(DnsResourceRecord *rr) {
_cleanup_free_ char *k = NULL, *t = NULL;
char *s;
int r;
assert(rr);
+ if (rr->to_string)
+ return rr->to_string;
+
r = dns_resource_key_to_string(rr->key, &k);
if (r < 0)
- return r;
+ return NULL;
switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) {
@@ -761,7 +793,7 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
rr->srv.port,
strna(rr->srv.name));
if (r < 0)
- return -ENOMEM;
+ return NULL;
break;
case DNS_TYPE_PTR:
@@ -770,25 +802,25 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
case DNS_TYPE_DNAME:
s = strjoin(k, " ", rr->ptr.name, NULL);
if (!s)
- return -ENOMEM;
+ return NULL;
break;
case DNS_TYPE_HINFO:
s = strjoin(k, " ", rr->hinfo.cpu, " ", rr->hinfo.os, NULL);
if (!s)
- return -ENOMEM;
+ return NULL;
break;
case DNS_TYPE_SPF: /* exactly the same as TXT */
case DNS_TYPE_TXT:
t = format_txt(rr->txt.items);
if (!t)
- return -ENOMEM;
+ return NULL;
s = strjoin(k, " ", t, NULL);
if (!s)
- return -ENOMEM;
+ return NULL;
break;
case DNS_TYPE_A: {
@@ -796,22 +828,22 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
r = in_addr_to_string(AF_INET, (const union in_addr_union*) &rr->a.in_addr, &x);
if (r < 0)
- return r;
+ return NULL;
s = strjoin(k, " ", x, NULL);
if (!s)
- return -ENOMEM;
+ return NULL;
break;
}
case DNS_TYPE_AAAA:
r = in_addr_to_string(AF_INET6, (const union in_addr_union*) &rr->aaaa.in6_addr, &t);
if (r < 0)
- return r;
+ return NULL;
s = strjoin(k, " ", t, NULL);
if (!s)
- return -ENOMEM;
+ return NULL;
break;
case DNS_TYPE_SOA:
@@ -825,7 +857,7 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
rr->soa.expire,
rr->soa.minimum);
if (r < 0)
- return -ENOMEM;
+ return NULL;
break;
case DNS_TYPE_MX:
@@ -834,7 +866,7 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
rr->mx.priority,
rr->mx.exchange);
if (r < 0)
- return -ENOMEM;
+ return NULL;
break;
case DNS_TYPE_LOC:
@@ -847,17 +879,17 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
rr->loc.horiz_pre,
rr->loc.vert_pre);
if (!t)
- return -ENOMEM;
+ return NULL;
s = strjoin(k, " ", t, NULL);
if (!s)
- return -ENOMEM;
+ return NULL;
break;
case DNS_TYPE_DS:
t = hexmem(rr->ds.digest, rr->ds.digest_size);
if (!t)
- return -ENOMEM;
+ return NULL;
r = asprintf(&s, "%s %u %u %u %s",
k,
@@ -866,13 +898,13 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
rr->ds.digest_type,
t);
if (r < 0)
- return -ENOMEM;
+ return NULL;
break;
case DNS_TYPE_SSHFP:
t = hexmem(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size);
if (!t)
- return -ENOMEM;
+ return NULL;
r = asprintf(&s, "%s %u %u %s",
k,
@@ -880,58 +912,62 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
rr->sshfp.fptype,
t);
if (r < 0)
- return -ENOMEM;
+ return NULL;
break;
case DNS_TYPE_DNSKEY: {
- const char *alg;
+ _cleanup_free_ char *alg = NULL;
- alg = dnssec_algorithm_to_string(rr->dnskey.algorithm);
+ r = dnssec_algorithm_to_string_alloc(rr->dnskey.algorithm, &alg);
+ if (r < 0)
+ return NULL;
t = base64mem(rr->dnskey.key, rr->dnskey.key_size);
if (!t)
- return -ENOMEM;
+ return NULL;
- r = asprintf(&s, "%s %u %u %.*s%.*u %s",
+ r = asprintf(&s, "%s %u %u %s %s",
k,
rr->dnskey.flags,
rr->dnskey.protocol,
- alg ? -1 : 0, alg,
- alg ? 0 : 1, alg ? 0u : (unsigned) rr->dnskey.algorithm,
+ alg,
t);
if (r < 0)
- return -ENOMEM;
+ return NULL;
break;
}
case DNS_TYPE_RRSIG: {
- const char *type, *alg;
+ _cleanup_free_ char *alg = NULL;
char expiration[strlen("YYYYMMDDHHmmSS") + 1], inception[strlen("YYYYMMDDHHmmSS") + 1];
+ const char *type;
type = dns_type_to_string(rr->rrsig.type_covered);
- alg = dnssec_algorithm_to_string(rr->rrsig.algorithm);
+
+ r = dnssec_algorithm_to_string_alloc(rr->rrsig.algorithm, &alg);
+ if (r < 0)
+ return NULL;
t = base64mem(rr->rrsig.signature, rr->rrsig.signature_size);
if (!t)
- return -ENOMEM;
+ return NULL;
r = format_timestamp_dns(expiration, sizeof(expiration), rr->rrsig.expiration);
if (r < 0)
- return r;
+ return NULL;
r = format_timestamp_dns(inception, sizeof(inception), rr->rrsig.inception);
if (r < 0)
- return r;
+ return NULL;
/* TYPE?? follows
* http://tools.ietf.org/html/rfc3597#section-5 */
- r = asprintf(&s, "%s %s%.*u %.*s%.*u %u %u %s %s %u %s %s",
+ r = asprintf(&s, "%s %s%.*u %s %u %u %s %s %u %s %s",
k,
type ?: "TYPE",
type ? 0 : 1, type ? 0u : (unsigned) rr->rrsig.type_covered,
- alg ? -1 : 0, alg,
- alg ? 0 : 1, alg ? 0u : (unsigned) rr->rrsig.algorithm,
+ alg,
rr->rrsig.labels,
rr->rrsig.original_ttl,
expiration,
@@ -940,21 +976,21 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
rr->rrsig.signer,
t);
if (r < 0)
- return -ENOMEM;
+ return NULL;
break;
}
case DNS_TYPE_NSEC:
t = format_types(rr->nsec.types);
if (!t)
- return -ENOMEM;
+ return NULL;
r = asprintf(&s, "%s %s %s",
k,
rr->nsec.next_domain_name,
t);
if (r < 0)
- return -ENOMEM;
+ return NULL;
break;
case DNS_TYPE_NSEC3: {
@@ -963,16 +999,16 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
if (rr->nsec3.salt_size > 0) {
salt = hexmem(rr->nsec3.salt, rr->nsec3.salt_size);
if (!salt)
- return -ENOMEM;
+ return NULL;
}
hash = base32hexmem(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, false);
if (!hash)
- return -ENOMEM;
+ return NULL;
t = format_types(rr->nsec3.types);
if (!t)
- return -ENOMEM;
+ return NULL;
r = asprintf(&s, "%s %"PRIu8" %"PRIu8" %"PRIu16" %s %s %s",
k,
@@ -983,7 +1019,7 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
hash,
t);
if (r < 0)
- return -ENOMEM;
+ return NULL;
break;
}
@@ -991,16 +1027,17 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
default:
t = hexmem(rr->generic.data, rr->generic.size);
if (!t)
- return -ENOMEM;
+ return NULL;
+ /* Format as documented in RFC 3597, Section 5 */
r = asprintf(&s, "%s \\# %zu %s", k, rr->generic.size, t);
if (r < 0)
- return -ENOMEM;
+ return NULL;
break;
}
- *ret = s;
- return 0;
+ rr->to_string = s;
+ return s;
}
int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) {
@@ -1048,34 +1085,157 @@ int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) {
return 0;
}
-const char *dns_class_to_string(uint16_t class) {
+static void dns_resource_record_hash_func(const void *i, struct siphash *state) {
+ const DnsResourceRecord *rr = i;
+
+ assert(rr);
+
+ dns_resource_key_hash_func(rr->key, state);
+
+ switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) {
+
+ case DNS_TYPE_SRV:
+ siphash24_compress(&rr->srv.priority, sizeof(rr->srv.priority), state);
+ siphash24_compress(&rr->srv.weight, sizeof(rr->srv.weight), state);
+ siphash24_compress(&rr->srv.port, sizeof(rr->srv.port), state);
+ dns_name_hash_func(rr->srv.name, state);
+ break;
+
+ case DNS_TYPE_PTR:
+ case DNS_TYPE_NS:
+ case DNS_TYPE_CNAME:
+ case DNS_TYPE_DNAME:
+ dns_name_hash_func(rr->ptr.name, state);
+ break;
+
+ case DNS_TYPE_HINFO:
+ string_hash_func(rr->hinfo.cpu, state);
+ string_hash_func(rr->hinfo.os, state);
+ break;
- switch (class) {
+ case DNS_TYPE_TXT:
+ case DNS_TYPE_SPF: {
+ DnsTxtItem *j;
- case DNS_CLASS_IN:
- return "IN";
+ LIST_FOREACH(items, j, rr->txt.items) {
+ siphash24_compress(j->data, j->length, state);
- case DNS_CLASS_ANY:
- return "ANY";
+ /* Add an extra NUL byte, so that "a" followed by "b" doesn't result in the same hash as "ab"
+ * followed by "". */
+ siphash24_compress_byte(0, state);
+ }
+ break;
}
- return NULL;
+ case DNS_TYPE_A:
+ siphash24_compress(&rr->a.in_addr, sizeof(rr->a.in_addr), state);
+ break;
+
+ case DNS_TYPE_AAAA:
+ siphash24_compress(&rr->aaaa.in6_addr, sizeof(rr->aaaa.in6_addr), state);
+ break;
+
+ case DNS_TYPE_SOA:
+ dns_name_hash_func(rr->soa.mname, state);
+ dns_name_hash_func(rr->soa.rname, state);
+ siphash24_compress(&rr->soa.serial, sizeof(rr->soa.serial), state);
+ siphash24_compress(&rr->soa.refresh, sizeof(rr->soa.refresh), state);
+ siphash24_compress(&rr->soa.retry, sizeof(rr->soa.retry), state);
+ siphash24_compress(&rr->soa.expire, sizeof(rr->soa.expire), state);
+ siphash24_compress(&rr->soa.minimum, sizeof(rr->soa.minimum), state);
+ break;
+
+ case DNS_TYPE_MX:
+ siphash24_compress(&rr->mx.priority, sizeof(rr->mx.priority), state);
+ dns_name_hash_func(rr->mx.exchange, state);
+ break;
+
+ case DNS_TYPE_LOC:
+ siphash24_compress(&rr->loc.version, sizeof(rr->loc.version), state);
+ siphash24_compress(&rr->loc.size, sizeof(rr->loc.size), state);
+ siphash24_compress(&rr->loc.horiz_pre, sizeof(rr->loc.horiz_pre), state);
+ siphash24_compress(&rr->loc.vert_pre, sizeof(rr->loc.vert_pre), state);
+ siphash24_compress(&rr->loc.latitude, sizeof(rr->loc.latitude), state);
+ siphash24_compress(&rr->loc.longitude, sizeof(rr->loc.longitude), state);
+ siphash24_compress(&rr->loc.altitude, sizeof(rr->loc.altitude), state);
+ break;
+
+ case DNS_TYPE_SSHFP:
+ siphash24_compress(&rr->sshfp.algorithm, sizeof(rr->sshfp.algorithm), state);
+ siphash24_compress(&rr->sshfp.fptype, sizeof(rr->sshfp.fptype), state);
+ siphash24_compress(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, state);
+ break;
+
+ case DNS_TYPE_DNSKEY:
+ siphash24_compress(&rr->dnskey.flags, sizeof(rr->dnskey.flags), state);
+ siphash24_compress(&rr->dnskey.protocol, sizeof(rr->dnskey.protocol), state);
+ siphash24_compress(&rr->dnskey.algorithm, sizeof(rr->dnskey.algorithm), state);
+ siphash24_compress(rr->dnskey.key, rr->dnskey.key_size, state);
+ break;
+
+ case DNS_TYPE_RRSIG:
+ siphash24_compress(&rr->rrsig.type_covered, sizeof(rr->rrsig.type_covered), state);
+ siphash24_compress(&rr->rrsig.algorithm, sizeof(rr->rrsig.algorithm), state);
+ siphash24_compress(&rr->rrsig.labels, sizeof(rr->rrsig.labels), state);
+ siphash24_compress(&rr->rrsig.original_ttl, sizeof(rr->rrsig.original_ttl), state);
+ siphash24_compress(&rr->rrsig.expiration, sizeof(rr->rrsig.expiration), state);
+ siphash24_compress(&rr->rrsig.inception, sizeof(rr->rrsig.inception), state);
+ siphash24_compress(&rr->rrsig.key_tag, sizeof(rr->rrsig.key_tag), state);
+ dns_name_hash_func(rr->rrsig.signer, state);
+ siphash24_compress(rr->rrsig.signature, rr->rrsig.signature_size, state);
+ break;
+
+ case DNS_TYPE_NSEC:
+ dns_name_hash_func(rr->nsec.next_domain_name, state);
+ /* FIXME: we leave out the type bitmap here. Hash
+ * would be better if we'd take it into account
+ * too. */
+ break;
+
+ case DNS_TYPE_DS:
+ siphash24_compress(&rr->ds.key_tag, sizeof(rr->ds.key_tag), state);
+ siphash24_compress(&rr->ds.algorithm, sizeof(rr->ds.algorithm), state);
+ siphash24_compress(&rr->ds.digest_type, sizeof(rr->ds.digest_type), state);
+ siphash24_compress(rr->ds.digest, rr->ds.digest_size, state);
+ break;
+
+ case DNS_TYPE_NSEC3:
+ siphash24_compress(&rr->nsec3.algorithm, sizeof(rr->nsec3.algorithm), state);
+ siphash24_compress(&rr->nsec3.flags, sizeof(rr->nsec3.flags), state);
+ siphash24_compress(&rr->nsec3.iterations, sizeof(rr->nsec3.iterations), state);
+ siphash24_compress(rr->nsec3.salt, rr->nsec3.salt_size, state);
+ siphash24_compress(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, state);
+ /* FIXME: We leave the bitmaps out */
+ break;
+
+ default:
+ siphash24_compress(rr->generic.data, rr->generic.size, state);
+ break;
+ }
}
-int dns_class_from_string(const char *s, uint16_t *class) {
- assert(s);
- assert(class);
+static int dns_resource_record_compare_func(const void *a, const void *b) {
+ const DnsResourceRecord *x = a, *y = b;
+ int ret;
- if (strcaseeq(s, "IN"))
- *class = DNS_CLASS_IN;
- else if (strcaseeq(s, "ANY"))
- *class = DNS_CLASS_ANY;
- else
- return -EINVAL;
+ ret = dns_resource_key_compare_func(x->key, y->key);
+ if (ret != 0)
+ return ret;
- return 0;
+ if (dns_resource_record_equal(x, y))
+ return 0;
+
+ /* This is a bit dirty, we don't implement proper odering, but
+ * the hashtable doesn't need ordering anyway, hence we don't
+ * care. */
+ return x < y ? -1 : 1;
}
+const struct hash_ops dns_resource_record_hash_ops = {
+ .hash = dns_resource_record_hash_func,
+ .compare = dns_resource_record_compare_func,
+};
+
DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i) {
DnsTxtItem *n;
@@ -1090,6 +1250,9 @@ DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i) {
bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) {
+ if (a == b)
+ return true;
+
if (!a != !b)
return false;
@@ -1106,6 +1269,7 @@ bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) {
}
static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = {
+ /* Mnemonics as listed on https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */
[DNSSEC_ALGORITHM_RSAMD5] = "RSAMD5",
[DNSSEC_ALGORITHM_DH] = "DH",
[DNSSEC_ALGORITHM_DSA] = "DSA",
@@ -1115,14 +1279,20 @@ static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] =
[DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1] = "RSASHA1-NSEC3-SHA1",
[DNSSEC_ALGORITHM_RSASHA256] = "RSASHA256",
[DNSSEC_ALGORITHM_RSASHA512] = "RSASHA512",
+ [DNSSEC_ALGORITHM_ECC_GOST] = "ECC-GOST",
+ [DNSSEC_ALGORITHM_ECDSAP256SHA256] = "ECDSAP256SHA256",
+ [DNSSEC_ALGORITHM_ECDSAP384SHA384] = "ECDSAP384SHA384",
[DNSSEC_ALGORITHM_INDIRECT] = "INDIRECT",
[DNSSEC_ALGORITHM_PRIVATEDNS] = "PRIVATEDNS",
[DNSSEC_ALGORITHM_PRIVATEOID] = "PRIVATEOID",
};
-DEFINE_STRING_TABLE_LOOKUP(dnssec_algorithm, int);
+DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(dnssec_algorithm, int, 255);
static const char* const dnssec_digest_table[_DNSSEC_DIGEST_MAX_DEFINED] = {
- [DNSSEC_DIGEST_SHA1] = "SHA1",
- [DNSSEC_DIGEST_SHA256] = "SHA256",
+ /* Names as listed on https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */
+ [DNSSEC_DIGEST_SHA1] = "SHA-1",
+ [DNSSEC_DIGEST_SHA256] = "SHA-256",
+ [DNSSEC_DIGEST_GOST_R_34_11_94] = "GOST_R_34.11-94",
+ [DNSSEC_DIGEST_SHA384] = "SHA-384",
};
-DEFINE_STRING_TABLE_LOOKUP(dnssec_digest, int);
+DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(dnssec_digest, int, 255);
diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h
index 5c2306ba96..fe29a41566 100644
--- a/src/resolve/resolved-dns-rr.h
+++ b/src/resolve/resolved-dns-rr.h
@@ -33,17 +33,10 @@ typedef struct DnsResourceKey DnsResourceKey;
typedef struct DnsResourceRecord DnsResourceRecord;
typedef struct DnsTxtItem DnsTxtItem;
-/* DNS record classes, see RFC 1035 */
-enum {
- DNS_CLASS_IN = 0x01,
- DNS_CLASS_ANY = 0xFF,
- _DNS_CLASS_MAX,
- _DNS_CLASS_INVALID = -1
-};
-
/* DNSKEY RR flags */
-#define DNSKEY_FLAG_ZONE_KEY (UINT16_C(1) << 8)
#define DNSKEY_FLAG_SEP (UINT16_C(1) << 0)
+#define DNSKEY_FLAG_REVOKE (UINT16_C(1) << 7)
+#define DNSKEY_FLAG_ZONE_KEY (UINT16_C(1) << 8)
/* mDNS RR flags */
#define MDNS_RR_CACHE_FLUSH (UINT16_C(1) << 15)
@@ -59,8 +52,11 @@ enum {
DNSSEC_ALGORITHM_RSASHA1,
DNSSEC_ALGORITHM_DSA_NSEC3_SHA1,
DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1,
- DNSSEC_ALGORITHM_RSASHA256 = 8, /* RFC 5702 */
- DNSSEC_ALGORITHM_RSASHA512 = 10, /* RFC 5702 */
+ DNSSEC_ALGORITHM_RSASHA256 = 8, /* RFC 5702 */
+ DNSSEC_ALGORITHM_RSASHA512 = 10, /* RFC 5702 */
+ DNSSEC_ALGORITHM_ECC_GOST = 12, /* RFC 5933 */
+ DNSSEC_ALGORITHM_ECDSAP256SHA256 = 13, /* RFC 6605 */
+ DNSSEC_ALGORITHM_ECDSAP384SHA384 = 14, /* RFC 6605 */
DNSSEC_ALGORITHM_INDIRECT = 252,
DNSSEC_ALGORITHM_PRIVATEDNS,
DNSSEC_ALGORITHM_PRIVATEOID,
@@ -71,15 +67,23 @@ enum {
* https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */
enum {
DNSSEC_DIGEST_SHA1 = 1,
- DNSSEC_DIGEST_SHA256 = 2,
+ DNSSEC_DIGEST_SHA256 = 2, /* RFC 4509 */
+ DNSSEC_DIGEST_GOST_R_34_11_94 = 3, /* RFC 5933 */
+ DNSSEC_DIGEST_SHA384 = 4, /* RFC 6605 */
_DNSSEC_DIGEST_MAX_DEFINED
};
+/* DNSSEC NSEC3 hash algorithms, see
+ * https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml */
+enum {
+ NSEC3_ALGORITHM_SHA1 = 1,
+ _NSEC3_ALGORITHM_MAX_DEFINED
+};
+
struct DnsResourceKey {
unsigned n_ref;
uint16_t class, type;
char *_name; /* don't access directy, use DNS_RESOURCE_KEY_NAME()! */
- bool cache_flush:1;
};
/* Creates a temporary resource key. This is only useful to quickly
@@ -104,7 +108,9 @@ struct DnsTxtItem {
struct DnsResourceRecord {
unsigned n_ref;
DnsResourceKey *key;
+ char *to_string;
uint32_t ttl;
+ usec_t expiry; /* RRSIG signature expiry */
bool unparseable:1;
bool wire_format_canonical:1;
void *wire_format;
@@ -114,7 +120,7 @@ struct DnsResourceRecord {
struct {
void *data;
size_t size;
- } generic;
+ } generic, opt;
struct {
uint16_t priority;
@@ -159,6 +165,7 @@ struct DnsResourceRecord {
char *exchange;
} mx;
+ /* https://tools.ietf.org/html/rfc1876 */
struct {
uint8_t version;
uint8_t size;
@@ -169,14 +176,6 @@ struct DnsResourceRecord {
uint32_t altitude;
} loc;
- struct {
- uint16_t key_tag;
- uint8_t algorithm;
- uint8_t digest_type;
- void *digest;
- size_t digest_size;
- } ds;
-
/* https://tools.ietf.org/html/rfc4255#section-3.1 */
struct {
uint8_t algorithm;
@@ -214,6 +213,15 @@ struct DnsResourceRecord {
Bitmap *types;
} nsec;
+ /* https://tools.ietf.org/html/rfc4034#section-5.1 */
+ struct {
+ uint16_t key_tag;
+ uint8_t algorithm;
+ uint8_t digest_type;
+ void *digest;
+ size_t digest_size;
+ } ds;
+
struct {
uint8_t algorithm;
uint8_t flags;
@@ -228,7 +236,7 @@ struct DnsResourceRecord {
};
static inline const char* DNS_RESOURCE_KEY_NAME(const DnsResourceKey *key) {
- if (_unlikely_(!key))
+ if (!key)
return NULL;
if (key->_name)
@@ -237,6 +245,27 @@ static inline const char* DNS_RESOURCE_KEY_NAME(const DnsResourceKey *key) {
return (char*) key + sizeof(DnsResourceKey);
}
+static inline const void* DNS_RESOURCE_RECORD_RDATA(DnsResourceRecord *rr) {
+ if (!rr)
+ return NULL;
+
+ if (!rr->wire_format)
+ return NULL;
+
+ assert(rr->wire_format_rdata_offset <= rr->wire_format_size);
+ return (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset;
+}
+
+static inline size_t DNS_RESOURCE_RECORD_RDATA_SIZE(DnsResourceRecord *rr) {
+ if (!rr)
+ return 0;
+ if (!rr->wire_format)
+ return 0;
+
+ assert(rr->wire_format_rdata_offset <= rr->wire_format_size);
+ return rr->wire_format_size - rr->wire_format_rdata_offset;
+}
+
DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name);
DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname);
int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name);
@@ -245,8 +274,9 @@ DnsResourceKey* dns_resource_key_ref(DnsResourceKey *key);
DnsResourceKey* dns_resource_key_unref(DnsResourceKey *key);
bool dns_resource_key_is_address(const DnsResourceKey *key);
int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b);
-int dns_resource_key_match_rr(const DnsResourceKey *key, const DnsResourceRecord *rr, const char *search_domain);
-int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRecord *rr, const char *search_domain);
+int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain);
+int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain);
+int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *soa);
int dns_resource_key_to_string(const DnsResourceKey *key, char **ret);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceKey*, dns_resource_key_unref);
@@ -261,7 +291,7 @@ DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr);
int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name);
int dns_resource_record_new_address(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name);
int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecord *b);
-int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret);
+const char* dns_resource_record_to_string(DnsResourceRecord *rr);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceRecord*, dns_resource_record_unref);
int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical);
@@ -269,13 +299,11 @@ int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical);
DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i);
bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b);
-const char *dns_class_to_string(uint16_t type);
-int dns_class_from_string(const char *name, uint16_t *class);
-
extern const struct hash_ops dns_resource_key_hash_ops;
+extern const struct hash_ops dns_resource_record_hash_ops;
-const char* dnssec_algorithm_to_string(int i) _const_;
+int dnssec_algorithm_to_string_alloc(int i, char **ret);
int dnssec_algorithm_from_string(const char *s) _pure_;
-const char *dnssec_digest_to_string(int i) _const_;
+int dnssec_digest_to_string_alloc(int i, char **ret);
int dnssec_digest_from_string(const char *s) _pure_;
diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c
index eae903526b..dd3609bd12 100644
--- a/src/resolve/resolved-dns-scope.c
+++ b/src/resolve/resolved-dns-scope.c
@@ -57,6 +57,23 @@ int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int
s->family = family;
s->resend_timeout = MULTICAST_RESEND_TIMEOUT_MIN_USEC;
+ s->dnssec_mode = _DNSSEC_MODE_INVALID;
+
+ if (protocol == DNS_PROTOCOL_DNS) {
+ /* Copy DNSSEC mode from the link if it is set there,
+ * otherwise take the manager's DNSSEC mode. Note that
+ * we copy this only at scope creation time, and do
+ * not update it from the on, even if the setting
+ * changes. */
+
+ if (l)
+ s->dnssec_mode = l->dnssec_mode;
+ if (s->dnssec_mode == _DNSSEC_MODE_INVALID)
+ s->dnssec_mode = m->dnssec_mode;
+ if (s->dnssec_mode == _DNSSEC_MODE_INVALID)
+ s->dnssec_mode = DNSSEC_NO;
+ }
+
LIST_PREPEND(scopes, m->dns_scopes, s);
dns_scope_llmnr_membership(s, true);
@@ -81,7 +98,8 @@ static void dns_scope_abort_transactions(DnsScope *s) {
* freed while we still look at it */
t->block_gc++;
- dns_transaction_complete(t, DNS_TRANSACTION_ABORTED);
+ if (DNS_TRANSACTION_IS_LIVE(t->state))
+ dns_transaction_complete(t, DNS_TRANSACTION_ABORTED);
t->block_gc--;
dns_transaction_free(t);
@@ -161,17 +179,15 @@ void dns_scope_packet_lost(DnsScope *s, usec_t usec) {
s->resend_timeout = MIN(s->resend_timeout * 2, MULTICAST_RESEND_TIMEOUT_MAX_USEC);
}
-int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) {
+static int dns_scope_emit_one(DnsScope *s, int fd, DnsPacket *p) {
union in_addr_union addr;
int ifindex = 0, r;
int family;
uint32_t mtu;
- size_t saved_size = 0;
assert(s);
assert(p);
assert(p->protocol == s->protocol);
- assert((s->protocol == DNS_PROTOCOL_DNS) != (fd < 0));
if (s->link) {
mtu = s->link->mtu;
@@ -180,30 +196,13 @@ int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) {
mtu = manager_find_mtu(s->manager);
switch (s->protocol) {
+
case DNS_PROTOCOL_DNS:
- assert(server);
+ assert(fd >= 0);
if (DNS_PACKET_QDCOUNT(p) > 1)
return -EOPNOTSUPP;
- if (server->possible_features >= DNS_SERVER_FEATURE_LEVEL_EDNS0) {
- bool edns_do;
- size_t packet_size;
-
- edns_do = server->possible_features >= DNS_SERVER_FEATURE_LEVEL_DO;
-
- if (server->possible_features >= DNS_SERVER_FEATURE_LEVEL_LARGE)
- packet_size = DNS_PACKET_UNICAST_SIZE_LARGE_MAX;
- else
- packet_size = server->received_udp_packet_max;
-
- r = dns_packet_append_opt_rr(p, packet_size, edns_do, &saved_size);
- if (r < 0)
- return r;
-
- DNS_PACKET_HEADER(p)->arcount = htobe16(be16toh(DNS_PACKET_HEADER(p)->arcount) + 1);
- }
-
if (p->size > DNS_PACKET_UNICAST_SIZE_MAX)
return -EMSGSIZE;
@@ -214,15 +213,11 @@ int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) {
if (r < 0)
return r;
- if (saved_size > 0) {
- dns_packet_truncate(p, saved_size);
-
- DNS_PACKET_HEADER(p)->arcount = htobe16(be16toh(DNS_PACKET_HEADER(p)->arcount) - 1);
- }
-
break;
case DNS_PROTOCOL_LLMNR:
+ assert(fd < 0);
+
if (DNS_PACKET_QDCOUNT(p) > 1)
return -EOPNOTSUPP;
@@ -249,6 +244,8 @@ int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) {
break;
case DNS_PROTOCOL_MDNS:
+ assert(fd < 0);
+
if (!ratelimit_test(&s->ratelimit))
return -EBUSY;
@@ -278,8 +275,39 @@ int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) {
return 1;
}
-static int dns_scope_socket(DnsScope *s, int type, int family, const union in_addr_union *address, uint16_t port, DnsServer **server) {
- DnsServer *srv = NULL;
+int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p) {
+ int r;
+
+ assert(s);
+ assert(p);
+ assert(p->protocol == s->protocol);
+ assert((s->protocol == DNS_PROTOCOL_DNS) == (fd >= 0));
+
+ do {
+ /* If there are multiple linked packets, set the TC bit in all but the last of them */
+ if (p->more) {
+ assert(p->protocol == DNS_PROTOCOL_MDNS);
+ dns_packet_set_flags(p, true, true);
+ }
+
+ r = dns_scope_emit_one(s, fd, p);
+ if (r < 0)
+ return r;
+
+ p = p->more;
+ } while (p);
+
+ return 0;
+}
+
+static int dns_scope_socket(
+ DnsScope *s,
+ int type,
+ int family,
+ const union in_addr_union *address,
+ DnsServer *server,
+ uint16_t port) {
+
_cleanup_close_ int fd = -1;
union sockaddr_union sa = {};
socklen_t salen;
@@ -287,31 +315,27 @@ static int dns_scope_socket(DnsScope *s, int type, int family, const union in_ad
int ret, r;
assert(s);
- assert((family == AF_UNSPEC) == !address);
- if (family == AF_UNSPEC) {
- srv = dns_scope_get_dns_server(s);
- if (!srv)
- return -ESRCH;
+ if (server) {
+ assert(family == AF_UNSPEC);
+ assert(!address);
- srv->possible_features = dns_server_possible_features(srv);
-
- if (type == SOCK_DGRAM && srv->possible_features < DNS_SERVER_FEATURE_LEVEL_UDP)
- return -EAGAIN;
-
- sa.sa.sa_family = srv->family;
- if (srv->family == AF_INET) {
+ sa.sa.sa_family = server->family;
+ if (server->family == AF_INET) {
sa.in.sin_port = htobe16(port);
- sa.in.sin_addr = srv->address.in;
+ sa.in.sin_addr = server->address.in;
salen = sizeof(sa.in);
- } else if (srv->family == AF_INET6) {
+ } else if (server->family == AF_INET6) {
sa.in6.sin6_port = htobe16(port);
- sa.in6.sin6_addr = srv->address.in6;
+ sa.in6.sin6_addr = server->address.in6;
sa.in6.sin6_scope_id = s->link ? s->link->ifindex : 0;
salen = sizeof(sa.in6);
} else
return -EAFNOSUPPORT;
} else {
+ assert(family != AF_UNSPEC);
+ assert(address);
+
sa.sa.sa_family = family;
if (family == AF_INET) {
@@ -369,21 +393,18 @@ static int dns_scope_socket(DnsScope *s, int type, int family, const union in_ad
if (r < 0 && errno != EINPROGRESS)
return -errno;
- if (server)
- *server = srv;
-
ret = fd;
fd = -1;
return ret;
}
-int dns_scope_udp_dns_socket(DnsScope *s, DnsServer **server) {
- return dns_scope_socket(s, SOCK_DGRAM, AF_UNSPEC, NULL, 53, server);
+int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port) {
+ return dns_scope_socket(s, SOCK_DGRAM, AF_UNSPEC, NULL, server, port);
}
-int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *address, uint16_t port, DnsServer **server) {
- return dns_scope_socket(s, SOCK_STREAM, family, address, port, server);
+int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port) {
+ return dns_scope_socket(s, SOCK_STREAM, family, address, server, port);
}
DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain) {
@@ -416,6 +437,10 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co
dns_name_equal(domain, "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0)
return DNS_SCOPE_NO;
+ /* Never respond to some of the domains listed in RFC6761 */
+ if (dns_name_endswith(domain, "invalid") > 0)
+ return DNS_SCOPE_NO;
+
/* Always honour search domains for routing queries. Note that
* we return DNS_SCOPE_YES here, rather than just
* DNS_SCOPE_MAYBE, which means wildcard scopes won't be
@@ -433,7 +458,11 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co
dns_name_endswith(domain, "8.e.f.ip6.arpa") == 0 &&
dns_name_endswith(domain, "9.e.f.ip6.arpa") == 0 &&
dns_name_endswith(domain, "a.e.f.ip6.arpa") == 0 &&
- dns_name_endswith(domain, "b.e.f.ip6.arpa") == 0)
+ dns_name_endswith(domain, "b.e.f.ip6.arpa") == 0 &&
+ /* If networks use .local in their private setups, they are supposed to also add .local to their search
+ * domains, which we already checked above. Otherwise, we consider .local specific to mDNS and won't
+ * send such queries ordinary DNS servers. */
+ dns_name_endswith(domain, "local") == 0)
return DNS_SCOPE_MAYBE;
return DNS_SCOPE_NO;
@@ -473,7 +502,7 @@ int dns_scope_good_key(DnsScope *s, DnsResourceKey *key) {
if (s->protocol == DNS_PROTOCOL_DNS) {
- /* On classic DNS, lookin up non-address RRs is always
+ /* On classic DNS, looking up non-address RRs is always
* fine. (Specifically, we want to permit looking up
* DNSKEY and DS records on the root and top-level
* domains.) */
@@ -756,7 +785,7 @@ DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key,
/* Refuse reusing transactions that completed based on cached
* data instead of a real packet, if that's requested. */
if (!cache_ok &&
- IN_SET(t->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_FAILURE) &&
+ IN_SET(t->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_RCODE_FAILURE) &&
t->answer_source != DNS_TRANSACTION_NETWORK)
return NULL;
@@ -789,7 +818,11 @@ static int dns_scope_make_conflict_packet(
0 /* (ad) */,
0 /* (cd) */,
0));
- random_bytes(&DNS_PACKET_HEADER(p)->id, sizeof(uint16_t));
+
+ /* For mDNS, the transaction ID should always be 0 */
+ if (s->protocol != DNS_PROTOCOL_MDNS)
+ random_bytes(&DNS_PACKET_HEADER(p)->id, sizeof(uint16_t));
+
DNS_PACKET_HEADER(p)->qdcount = htobe16(1);
DNS_PACKET_HEADER(p)->arcount = htobe16(1);
@@ -830,7 +863,7 @@ static int on_conflict_dispatch(sd_event_source *es, usec_t usec, void *userdata
return 0;
}
- r = dns_scope_emit(scope, -1, NULL, p);
+ r = dns_scope_emit_udp(scope, -1, p);
if (r < 0)
log_debug_errno(r, "Failed to send conflict packet: %m");
}
@@ -879,6 +912,8 @@ int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr) {
if (r < 0)
return log_debug_errno(r, "Failed to add conflict dispatch event: %m");
+ (void) sd_event_source_set_description(scope->conflict_event_source, "scope-conflict");
+
return 0;
}
diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h
index 2fc2e07deb..a0676bd30e 100644
--- a/src/resolve/resolved-dns-scope.h
+++ b/src/resolve/resolved-dns-scope.h
@@ -82,9 +82,9 @@ DnsScope* dns_scope_free(DnsScope *s);
void dns_scope_packet_received(DnsScope *s, usec_t rtt);
void dns_scope_packet_lost(DnsScope *s, usec_t usec);
-int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p);
-int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *address, uint16_t port, DnsServer **server);
-int dns_scope_udp_dns_socket(DnsScope *s, DnsServer **server);
+int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p);
+int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port);
+int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port);
DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain);
int dns_scope_good_key(DnsScope *s, DnsResourceKey *key);
diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c
index d565f99c09..0969e31e8a 100644
--- a/src/resolve/resolved-dns-server.c
+++ b/src/resolve/resolved-dns-server.c
@@ -68,8 +68,8 @@ int dns_server_new(
s->n_ref = 1;
s->manager = m;
- s->verified_features = _DNS_SERVER_FEATURE_LEVEL_INVALID;
- s->possible_features = DNS_SERVER_FEATURE_LEVEL_BEST;
+ s->verified_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID;
+ s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST;
s->features_grace_period_usec = DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC;
s->received_udp_packet_max = DNS_PACKET_UNICAST_SIZE_MAX;
s->type = type;
@@ -135,6 +135,7 @@ DnsServer* dns_server_unref(DnsServer *s) {
if (s->n_ref > 0)
return NULL;
+ free(s->server_string);
free(s);
return NULL;
}
@@ -224,43 +225,66 @@ void dns_server_move_back_and_unmark(DnsServer *s) {
}
}
-void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel features, usec_t rtt, size_t size) {
+static void dns_server_verified(DnsServer *s, DnsServerFeatureLevel level) {
assert(s);
- if (features == DNS_SERVER_FEATURE_LEVEL_LARGE) {
- /* even if we successfully receive a reply to a request announcing
- support for large packets, that does not mean we can necessarily
- receive large packets. */
- if (s->verified_features < DNS_SERVER_FEATURE_LEVEL_LARGE - 1) {
- s->verified_features = DNS_SERVER_FEATURE_LEVEL_LARGE - 1;
- assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0);
- }
- } else if (s->verified_features < features) {
- s->verified_features = features;
- assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0);
+ if (s->verified_feature_level > level)
+ return;
+
+ if (s->verified_feature_level != level) {
+ log_debug("Verified feature level %s.", dns_server_feature_level_to_string(level));
+ s->verified_feature_level = level;
}
- if (s->possible_features == features)
- s->n_failed_attempts = 0;
+ assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0);
+}
+
+void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t rtt, size_t size) {
+ assert(s);
+
+ if (protocol == IPPROTO_UDP) {
+ if (s->possible_feature_level == level)
+ s->n_failed_udp = 0;
+
+ if (level == DNS_SERVER_FEATURE_LEVEL_LARGE)
+ /* Even if we successfully receive a reply to a request announcing support for large packets,
+ that does not mean we can necessarily receive large packets. */
+ dns_server_verified(s, DNS_SERVER_FEATURE_LEVEL_LARGE - 1);
+ else
+ /* A successful UDP reply, verifies UDP, ENDS0 and DO levels */
+ dns_server_verified(s, level);
+
+ } else if (protocol == IPPROTO_TCP) {
+
+ if (s->possible_feature_level == level)
+ s->n_failed_tcp = 0;
+
+ /* Successful TCP connections are only useful to verify the TCP feature level. */
+ dns_server_verified(s, DNS_SERVER_FEATURE_LEVEL_TCP);
+ }
/* Remember the size of the largest UDP packet we received from a server,
we know that we can always announce support for packets with at least
this size. */
- if (s->received_udp_packet_max < size)
+ if (protocol == IPPROTO_UDP && s->received_udp_packet_max < size)
s->received_udp_packet_max = size;
if (s->max_rtt < rtt) {
s->max_rtt = rtt;
- s->resend_timeout = MIN(MAX(DNS_TIMEOUT_MIN_USEC, s->max_rtt * 2), DNS_TIMEOUT_MAX_USEC);
+ s->resend_timeout = CLAMP(s->max_rtt * 2, DNS_TIMEOUT_MIN_USEC, DNS_TIMEOUT_MAX_USEC);
}
}
-void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel features, usec_t usec) {
+void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t usec) {
assert(s);
assert(s->manager);
- if (s->possible_features == features)
- s->n_failed_attempts ++;
+ if (s->possible_feature_level == level) {
+ if (protocol == IPPROTO_UDP)
+ s->n_failed_udp ++;
+ else if (protocol == IPPROTO_TCP)
+ s->n_failed_tcp ++;
+ }
if (s->resend_timeout > usec)
return;
@@ -268,14 +292,37 @@ void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel features, usec_t
s->resend_timeout = MIN(s->resend_timeout * 2, DNS_TIMEOUT_MAX_USEC);
}
-void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel features) {
+void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level) {
assert(s);
assert(s->manager);
- if (s->possible_features != features)
+ /* Invoked whenever we get a FORMERR, SERVFAIL or NOTIMP rcode from a server. */
+
+ if (s->possible_feature_level != level)
return;
- s->n_failed_attempts = (unsigned) -1;
+ s->packet_failed = true;
+}
+
+void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level) {
+ assert(s);
+ assert(s->manager);
+
+ /* Invoked whenever we get a packet with TC bit set. */
+
+ if (s->possible_feature_level != level)
+ return;
+
+ s->packet_truncated = true;
+}
+
+void dns_server_packet_rrsig_missing(DnsServer *s) {
+ assert(s);
+ assert(s->manager);
+
+ log_warning("DNS server %s does not augment replies with RRSIG records, DNSSEC not available.", dns_server_string(s));
+
+ s->rrsig_missing = true;
}
static bool dns_server_grace_period_expired(DnsServer *s) {
@@ -297,35 +344,138 @@ static bool dns_server_grace_period_expired(DnsServer *s) {
return true;
}
-DnsServerFeatureLevel dns_server_possible_features(DnsServer *s) {
+static void dns_server_reset_counters(DnsServer *s) {
assert(s);
- if (s->possible_features != DNS_SERVER_FEATURE_LEVEL_BEST &&
+ s->n_failed_udp = 0;
+ s->n_failed_tcp = 0;
+ s->packet_failed = false;
+ s->packet_truncated = false;
+ s->verified_usec = 0;
+}
+
+DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) {
+ assert(s);
+
+ if (s->possible_feature_level != DNS_SERVER_FEATURE_LEVEL_BEST &&
dns_server_grace_period_expired(s)) {
- _cleanup_free_ char *ip = NULL;
-
- s->possible_features = DNS_SERVER_FEATURE_LEVEL_BEST;
- s->n_failed_attempts = 0;
- s->verified_usec = 0;
-
- in_addr_to_string(s->family, &s->address, &ip);
- log_info("Grace period over, resuming full feature set for DNS server %s", strna(ip));
- } else if (s->possible_features <= s->verified_features)
- s->possible_features = s->verified_features;
- else if (s->n_failed_attempts >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS &&
- s->possible_features > DNS_SERVER_FEATURE_LEVEL_WORST) {
- _cleanup_free_ char *ip = NULL;
-
- s->possible_features --;
- s->n_failed_attempts = 0;
- s->verified_usec = 0;
-
- in_addr_to_string(s->family, &s->address, &ip);
- log_warning("Using degraded feature set (%s) for DNS server %s",
- dns_server_feature_level_to_string(s->possible_features), strna(ip));
+
+ s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST;
+ s->rrsig_missing = false;
+
+ dns_server_reset_counters(s);
+
+ log_info("Grace period over, resuming full feature set (%s) for DNS server %s",
+ dns_server_feature_level_to_string(s->possible_feature_level),
+ dns_server_string(s));
+
+ } else if (s->possible_feature_level <= s->verified_feature_level)
+ s->possible_feature_level = s->verified_feature_level;
+ else {
+ DnsServerFeatureLevel p = s->possible_feature_level;
+
+ if (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS &&
+ s->possible_feature_level == DNS_SERVER_FEATURE_LEVEL_TCP)
+
+ /* We are at the TCP (lowest) level, and we tried a couple of TCP connections, and it didn't
+ * work. Upgrade back to UDP again. */
+ s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_UDP;
+
+ else if ((s->n_failed_udp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS &&
+ s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_UDP) ||
+ (s->packet_failed &&
+ s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) ||
+ (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS &&
+ s->packet_truncated &&
+ s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP))
+
+ /* Downgrade the feature one level, maybe things will work better then. We do this under any of
+ * three conditions:
+ *
+ * 1. We lost too many UDP packets in a row, and are on a feature level of UDP or higher. If
+ * the packets are lost, maybe the server cannot parse them, hence downgrading sounds like a
+ * good idea. We might downgrade all the way down to TCP this way.
+ *
+ * 2. We got a failure packet, and are at a feature level above UDP. Note that in this case we
+ * downgrade no further than UDP, under the assumption that a failure packet indicates an
+ * incompatible packet contents, but not a problem with the transport.
+ *
+ * 3. We got too many TCP connection failures in a row, we had at least one truncated packet,
+ * and are on a feature level above UDP. By downgrading things and getting rid of DNSSEC or
+ * EDNS0 data we hope to make the packet smaller, so that it still works via UDP given that
+ * TCP appears not to be a fallback. Note that if we are already at the lowest UDP level, we
+ * don't go further down, since that's TCP, and TCP failed too often after all.
+ */
+
+ s->possible_feature_level--;
+
+ if (p != s->possible_feature_level) {
+
+ /* We changed the feature level, reset the counting */
+ dns_server_reset_counters(s);
+
+ log_warning("Using degraded feature set (%s) for DNS server %s",
+ dns_server_feature_level_to_string(s->possible_feature_level),
+ dns_server_string(s));
+ }
}
- return s->possible_features;
+ return s->possible_feature_level;
+}
+
+int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level) {
+ size_t packet_size;
+ bool edns_do;
+ int r;
+
+ assert(server);
+ assert(packet);
+ assert(packet->protocol == DNS_PROTOCOL_DNS);
+
+ /* Fix the OPT field in the packet to match our current feature level. */
+
+ r = dns_packet_truncate_opt(packet);
+ if (r < 0)
+ return r;
+
+ if (level < DNS_SERVER_FEATURE_LEVEL_EDNS0)
+ return 0;
+
+ edns_do = level >= DNS_SERVER_FEATURE_LEVEL_DO;
+
+ if (level >= DNS_SERVER_FEATURE_LEVEL_LARGE)
+ packet_size = DNS_PACKET_UNICAST_SIZE_LARGE_MAX;
+ else
+ packet_size = server->received_udp_packet_max;
+
+ return dns_packet_append_opt(packet, packet_size, edns_do, NULL);
+}
+
+const char *dns_server_string(DnsServer *server) {
+ assert(server);
+
+ if (!server->server_string)
+ (void) in_addr_to_string(server->family, &server->address, &server->server_string);
+
+ return strna(server->server_string);
+}
+
+bool dns_server_dnssec_supported(DnsServer *server) {
+ assert(server);
+
+ /* Returns whether the server supports DNSSEC according to what we know about it */
+
+ if (server->possible_feature_level < DNS_SERVER_FEATURE_LEVEL_DO)
+ return false;
+
+ if (server->rrsig_missing)
+ return false;
+
+ /* DNSSEC servers need to support TCP properly (see RFC5966), if they don't, we assume DNSSEC is borked too */
+ if (server->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS)
+ return false;
+
+ return true;
}
static void dns_server_hash_func(const void *p, struct siphash *state) {
@@ -419,12 +569,8 @@ DnsServer *manager_set_dns_server(Manager *m, DnsServer *s) {
if (m->current_dns_server == s)
return s;
- if (s) {
- _cleanup_free_ char *ip = NULL;
-
- in_addr_to_string(s->family, &s->address, &ip);
- log_info("Switching to system DNS server %s.", strna(ip));
- }
+ if (s)
+ log_info("Switching to system DNS server %s.", dns_server_string(s));
dns_server_unref(m->current_dns_server);
m->current_dns_server = dns_server_ref(s);
diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h
index b07fc3af3d..323f702903 100644
--- a/src/resolve/resolved-dns-server.h
+++ b/src/resolve/resolved-dns-server.h
@@ -61,18 +61,30 @@ struct DnsServer {
int family;
union in_addr_union address;
- bool marked:1;
+ char *server_string;
usec_t resend_timeout;
usec_t max_rtt;
- DnsServerFeatureLevel verified_features;
- DnsServerFeatureLevel possible_features;
+ DnsServerFeatureLevel verified_feature_level;
+ DnsServerFeatureLevel possible_feature_level;
size_t received_udp_packet_max;
- unsigned n_failed_attempts;
+ unsigned n_failed_udp;
+ unsigned n_failed_tcp;
+ bool packet_failed:1;
+ bool packet_truncated:1;
usec_t verified_usec;
usec_t features_grace_period_usec;
+ /* Indicates whether responses are augmented with RRSIG by
+ * server or not. Note that this is orthogonal to the feature
+ * level stuff, as it's only information describing responses,
+ * and has no effect on how the questions are asked. */
+ bool rrsig_missing:1;
+
+ /* Used when GC'ing old DNS servers when configuration changes. */
+ bool marked:1;
+
/* If linked is set, then this server appears in the servers linked list */
bool linked:1;
LIST_FIELDS(DnsServer, servers);
@@ -92,9 +104,19 @@ DnsServer* dns_server_unref(DnsServer *s);
void dns_server_unlink(DnsServer *s);
void dns_server_move_back_and_unmark(DnsServer *s);
-void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel features, usec_t rtt, size_t size);
-void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel features, usec_t usec);
-void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel features);
+void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t rtt, size_t size);
+void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t usec);
+void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level);
+void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level);
+void dns_server_packet_rrsig_missing(DnsServer *s);
+
+DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s);
+
+int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level);
+
+const char *dns_server_string(DnsServer *server);
+
+bool dns_server_dnssec_supported(DnsServer *server);
DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr);
@@ -110,6 +132,4 @@ void manager_next_dns_server(Manager *m);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref);
-DnsServerFeatureLevel dns_server_possible_features(DnsServer *s);
-
extern const struct hash_ops dns_server_hash_ops;
diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c
index 1c501182fb..b72e6cc06f 100644
--- a/src/resolve/resolved-dns-stream.c
+++ b/src/resolve/resolved-dns-stream.c
@@ -347,7 +347,6 @@ DnsStream *dns_stream_free(DnsStream *s) {
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_free);
int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) {
- static const int one = 1;
_cleanup_(dns_stream_freep) DnsStream *s = NULL;
int r;
@@ -364,14 +363,12 @@ int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) {
s->fd = -1;
s->protocol = protocol;
- r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
- if (r < 0)
- return -errno;
-
r = sd_event_add_io(m->event, &s->io_event_source, fd, EPOLLIN, on_stream_io, s);
if (r < 0)
return r;
+ (void) sd_event_source_set_description(s->io_event_source, "dns-stream-io");
+
r = sd_event_add_time(
m->event,
&s->timeout_event_source,
@@ -381,6 +378,8 @@ int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) {
if (r < 0)
return r;
+ (void) sd_event_source_set_description(s->timeout_event_source, "dns-stream-timeout");
+
LIST_PREPEND(streams, m->dns_streams, s);
s->manager = m;
s->fd = fd;
diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c
index 90f07e6c4b..9ee10f21c8 100644
--- a/src/resolve/resolved-dns-transaction.c
+++ b/src/resolve/resolved-dns-transaction.c
@@ -19,6 +19,8 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
+#include <sd-messages.h>
+
#include "af-list.h"
#include "alloc-util.h"
#include "dns-domain.h"
@@ -29,25 +31,49 @@
#include "resolved-llmnr.h"
#include "string-table.h"
+static void dns_transaction_reset_answer(DnsTransaction *t) {
+ assert(t);
+
+ t->received = dns_packet_unref(t->received);
+ t->answer = dns_answer_unref(t->answer);
+ t->answer_rcode = 0;
+ t->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
+ t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID;
+ t->answer_authenticated = false;
+ t->answer_nsec_ttl = (uint32_t) -1;
+}
+
+static void dns_transaction_close_connection(DnsTransaction *t) {
+ assert(t);
+
+ t->stream = dns_stream_free(t->stream);
+ t->dns_udp_event_source = sd_event_source_unref(t->dns_udp_event_source);
+ t->dns_udp_fd = safe_close(t->dns_udp_fd);
+}
+
+static void dns_transaction_stop_timeout(DnsTransaction *t) {
+ assert(t);
+
+ t->timeout_event_source = sd_event_source_unref(t->timeout_event_source);
+}
+
DnsTransaction* dns_transaction_free(DnsTransaction *t) {
DnsQueryCandidate *c;
DnsZoneItem *i;
+ DnsTransaction *z;
if (!t)
return NULL;
- sd_event_source_unref(t->timeout_event_source);
+ log_debug("Freeing transaction %" PRIu16 ".", t->id);
- dns_packet_unref(t->sent);
- dns_packet_unref(t->received);
-
- dns_answer_unref(t->answer);
+ dns_transaction_close_connection(t);
+ dns_transaction_stop_timeout(t);
- sd_event_source_unref(t->dns_udp_event_source);
- safe_close(t->dns_udp_fd);
+ dns_packet_unref(t->sent);
+ dns_transaction_reset_answer(t);
dns_server_unref(t->server);
- dns_stream_free(t->stream);
if (t->scope) {
hashmap_remove_value(t->scope->transactions_by_key, t->key, t);
@@ -57,16 +83,27 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) {
hashmap_remove(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id));
}
- dns_resource_key_unref(t->key);
-
- while ((c = set_steal_first(t->query_candidates)))
+ while ((c = set_steal_first(t->notify_query_candidates)))
set_remove(c->transactions, t);
+ set_free(t->notify_query_candidates);
- set_free(t->query_candidates);
-
- while ((i = set_steal_first(t->zone_items)))
+ while ((i = set_steal_first(t->notify_zone_items)))
i->probe_transaction = NULL;
- set_free(t->zone_items);
+ set_free(t->notify_zone_items);
+
+ while ((z = set_steal_first(t->notify_transactions)))
+ set_remove(z->dnssec_transactions, t);
+ set_free(t->notify_transactions);
+
+ while ((z = set_steal_first(t->dnssec_transactions))) {
+ set_remove(z->notify_transactions, t);
+ dns_transaction_gc(z);
+ }
+ set_free(t->dnssec_transactions);
+
+ dns_answer_unref(t->validated_keys);
+ dns_resource_key_unref(t->key);
+ free(t->key_string);
free(t);
return NULL;
@@ -74,14 +111,20 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) {
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsTransaction*, dns_transaction_free);
-void dns_transaction_gc(DnsTransaction *t) {
+bool dns_transaction_gc(DnsTransaction *t) {
assert(t);
if (t->block_gc > 0)
- return;
+ return true;
- if (set_isempty(t->query_candidates) && set_isempty(t->zone_items))
+ if (set_isempty(t->notify_query_candidates) &&
+ set_isempty(t->notify_zone_items) &&
+ set_isempty(t->notify_transactions)) {
dns_transaction_free(t);
+ return false;
+ }
+
+ return true;
}
int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) {
@@ -92,6 +135,16 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
assert(s);
assert(key);
+ /* Don't allow looking up invalid or pseudo RRs */
+ if (!dns_type_is_valid_query(key->type))
+ return -EINVAL;
+ if (dns_type_is_obsolete(key->type))
+ return -EOPNOTSUPP;
+
+ /* We only support the IN class */
+ if (key->class != DNS_CLASS_IN && key->class != DNS_CLASS_ANY)
+ return -EOPNOTSUPP;
+
r = hashmap_ensure_allocated(&s->manager->dns_transactions, NULL);
if (r < 0)
return r;
@@ -106,7 +159,10 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
t->dns_udp_fd = -1;
t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID;
+ t->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
+ t->answer_nsec_ttl = (uint32_t) -1;
t->key = dns_resource_key_ref(key);
+ t->current_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID;
/* Find a fresh, unused transaction id */
do
@@ -129,6 +185,8 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
LIST_PREPEND(transactions_by_scope, s->transactions, t);
t->scope = s;
+ s->manager->n_transactions_total ++;
+
if (ret)
*ret = t;
@@ -137,16 +195,6 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
return 0;
}
-static void dns_transaction_stop(DnsTransaction *t) {
- assert(t);
-
- t->timeout_event_source = sd_event_source_unref(t->timeout_event_source);
- t->stream = dns_stream_free(t->stream);
-
- /* Note that we do not drop the UDP socket here, as we want to
- * reuse it to repeat the interaction. */
-}
-
static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) {
_cleanup_free_ char *pretty = NULL;
DnsZoneItem *z;
@@ -159,7 +207,9 @@ static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) {
in_addr_to_string(p->family, &p->sender, &pretty);
- log_debug("Transaction on scope %s on %s/%s got tentative packet from %s",
+ log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s got tentative packet from %s.",
+ t->id,
+ dns_transaction_key_string(t),
dns_protocol_to_string(t->scope->protocol),
t->scope->link ? t->scope->link->name : "*",
t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family),
@@ -175,7 +225,7 @@ static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) {
log_debug("We have the lexicographically larger IP address and thus lost in the conflict.");
t->block_gc++;
- while ((z = set_first(t->zone_items))) {
+ while ((z = set_first(t->notify_zone_items))) {
/* First, make sure the zone item drops the reference
* to us */
dns_zone_item_probe_stop(z);
@@ -192,38 +242,118 @@ static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) {
void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
DnsQueryCandidate *c;
DnsZoneItem *z;
+ DnsTransaction *d;
Iterator i;
assert(t);
- assert(!IN_SET(state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING));
+ assert(!DNS_TRANSACTION_IS_LIVE(state));
+
+ if (state == DNS_TRANSACTION_DNSSEC_FAILED)
+ log_struct(LOG_NOTICE,
+ LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_FAILURE),
+ LOG_MESSAGE("DNSSEC validation failed for question %s: %s", dns_transaction_key_string(t), dnssec_result_to_string(t->answer_dnssec_result)),
+ "DNS_TRANSACTION=%" PRIu16, t->id,
+ "DNS_QUESTION=%s", dns_transaction_key_string(t),
+ "DNSSEC_RESULT=%s", dnssec_result_to_string(t->answer_dnssec_result),
+ NULL);
/* Note that this call might invalidate the query. Callers
* should hence not attempt to access the query or transaction
* after calling this function. */
- log_debug("Transaction on scope %s on %s/%s now complete with <%s> from %s",
+ log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s now complete with <%s> from %s (%s).",
+ t->id,
+ dns_transaction_key_string(t),
dns_protocol_to_string(t->scope->protocol),
t->scope->link ? t->scope->link->name : "*",
t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family),
dns_transaction_state_to_string(state),
- t->answer_source < 0 ? "none" : dns_transaction_source_to_string(t->answer_source));
+ t->answer_source < 0 ? "none" : dns_transaction_source_to_string(t->answer_source),
+ t->answer_authenticated ? "authenticated" : "unsigned");
t->state = state;
- dns_transaction_stop(t);
+ dns_transaction_close_connection(t);
+ dns_transaction_stop_timeout(t);
/* Notify all queries that are interested, but make sure the
* transaction isn't freed while we are still looking at it */
t->block_gc++;
- SET_FOREACH(c, t->query_candidates, i)
- dns_query_candidate_ready(c);
- SET_FOREACH(z, t->zone_items, i)
- dns_zone_item_ready(z);
- t->block_gc--;
+ SET_FOREACH(c, t->notify_query_candidates, i)
+ dns_query_candidate_notify(c);
+ SET_FOREACH(z, t->notify_zone_items, i)
+ dns_zone_item_notify(z);
+
+ if (!set_isempty(t->notify_transactions)) {
+ DnsTransaction **nt;
+ unsigned j, n = 0;
+
+ /* We need to be careful when notifying other
+ * transactions, as that might destroy other
+ * transactions in our list. Hence, in order to be
+ * able to safely iterate through the list of
+ * transactions, take a GC lock on all of them
+ * first. Then, in a second loop, notify them, but
+ * first unlock that specific transaction. */
+
+ nt = newa(DnsTransaction*, set_size(t->notify_transactions));
+ SET_FOREACH(d, t->notify_transactions, i) {
+ nt[n++] = d;
+ d->block_gc++;
+ }
+
+ assert(n == set_size(t->notify_transactions));
+
+ for (j = 0; j < n; j++) {
+ if (set_contains(t->notify_transactions, nt[j]))
+ dns_transaction_notify(nt[j], t);
+
+ nt[j]->block_gc--;
+ dns_transaction_gc(nt[j]);
+ }
+ }
+
+ t->block_gc--;
dns_transaction_gc(t);
}
+static int dns_transaction_pick_server(DnsTransaction *t) {
+ DnsServer *server;
+
+ assert(t);
+ assert(t->scope->protocol == DNS_PROTOCOL_DNS);
+
+ server = dns_scope_get_dns_server(t->scope);
+ if (!server)
+ return -ESRCH;
+
+ t->current_feature_level = dns_server_possible_feature_level(server);
+
+ if (server == t->server)
+ return 0;
+
+ dns_server_unref(t->server);
+ t->server = dns_server_ref(server);
+
+ return 1;
+}
+
+static void dns_transaction_retry(DnsTransaction *t) {
+ int r;
+
+ assert(t);
+
+ log_debug("Retrying transaction %" PRIu16 ".", t->id);
+
+ /* Before we try again, switch to a new server. */
+ dns_scope_next_dns_server(t->scope);
+
+ r = dns_transaction_go(t);
+ if (r < 0)
+ dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
+}
+
static int on_stream_complete(DnsStream *s, int error) {
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
DnsTransaction *t;
@@ -238,6 +368,16 @@ static int on_stream_complete(DnsStream *s, int error) {
t->stream = dns_stream_free(t->stream);
+ if (ERRNO_IS_DISCONNECT(error)) {
+ usec_t usec;
+
+ log_debug_errno(error, "Connection failure for DNS TCP stream: %m");
+ assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &usec) >= 0);
+ dns_server_packet_lost(t->server, IPPROTO_TCP, t->current_feature_level, usec - t->start_usec);
+
+ dns_transaction_retry(t);
+ return 0;
+ }
if (error != 0) {
dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
return 0;
@@ -255,32 +395,46 @@ static int on_stream_complete(DnsStream *s, int error) {
dns_transaction_process_reply(t, p);
t->block_gc--;
- /* If the response wasn't useful, then complete the transition now */
+ /* If the response wasn't useful, then complete the transition
+ * now. After all, we are the worst feature set now with TCP
+ * sockets, and there's really no point in retrying. */
if (t->state == DNS_TRANSACTION_PENDING)
dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
+ else
+ dns_transaction_gc(t);
return 0;
}
static int dns_transaction_open_tcp(DnsTransaction *t) {
- DnsServer *server = NULL;
_cleanup_close_ int fd = -1;
int r;
assert(t);
- if (t->stream)
- return 0;
+ dns_transaction_close_connection(t);
switch (t->scope->protocol) {
+
case DNS_PROTOCOL_DNS:
- fd = dns_scope_tcp_socket(t->scope, AF_UNSPEC, NULL, 53, &server);
+ r = dns_transaction_pick_server(t);
+ if (r < 0)
+ return r;
+
+ if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type))
+ return -EOPNOTSUPP;
+
+ r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level);
+ if (r < 0)
+ return r;
+
+ fd = dns_scope_socket_tcp(t->scope, AF_UNSPEC, NULL, t->server, 53);
break;
case DNS_PROTOCOL_LLMNR:
/* When we already received a reply to this (but it was truncated), send to its sender address */
if (t->received)
- fd = dns_scope_tcp_socket(t->scope, t->received->family, &t->received->sender, t->received->sender_port, NULL);
+ fd = dns_scope_socket_tcp(t->scope, t->received->family, &t->received->sender, NULL, t->received->sender_port);
else {
union in_addr_union address;
int family = AF_UNSPEC;
@@ -297,7 +451,7 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
if (family != t->scope->family)
return -ESRCH;
- fd = dns_scope_tcp_socket(t->scope, family, &address, LLMNR_PORT, NULL);
+ fd = dns_scope_socket_tcp(t->scope, family, &address, NULL, LLMNR_PORT);
}
break;
@@ -312,7 +466,6 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
r = dns_stream_new(t->scope->manager, &t->stream, t->scope->protocol, fd);
if (r < 0)
return r;
-
fd = -1;
r = dns_stream_write_packet(t->stream, t->sent);
@@ -321,11 +474,6 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
return r;
}
- dns_server_unref(t->server);
- t->server = dns_server_ref(server);
- t->received = dns_packet_unref(t->received);
- t->answer = dns_answer_unref(t->answer);
- t->answer_rcode = 0;
t->stream->complete = on_stream_complete;
t->stream->transaction = t;
@@ -335,17 +483,92 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
if (t->scope->link)
t->stream->ifindex = t->scope->link->ifindex;
+ dns_transaction_reset_answer(t);
+
+ t->tried_stream = true;
+
return 0;
}
-static void dns_transaction_next_dns_server(DnsTransaction *t) {
+static void dns_transaction_cache_answer(DnsTransaction *t) {
assert(t);
- t->server = dns_server_unref(t->server);
- t->dns_udp_event_source = sd_event_source_unref(t->dns_udp_event_source);
- t->dns_udp_fd = safe_close(t->dns_udp_fd);
+ /* For mDNS we cache whenever we get the packet, rather than
+ * in each transaction. */
+ if (!IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR))
+ return;
- dns_scope_next_dns_server(t->scope);
+ /* We never cache if this packet is from the local host, under
+ * the assumption that a locally running DNS server would
+ * cache this anyway, and probably knows better when to flush
+ * the cache then we could. */
+ if (!DNS_PACKET_SHALL_CACHE(t->received))
+ return;
+
+ dns_cache_put(&t->scope->cache,
+ t->key,
+ t->answer_rcode,
+ t->answer,
+ t->answer_authenticated,
+ t->answer_nsec_ttl,
+ 0,
+ t->received->family,
+ &t->received->sender);
+}
+
+static bool dns_transaction_dnssec_is_live(DnsTransaction *t) {
+ DnsTransaction *dt;
+ Iterator i;
+
+ assert(t);
+
+ SET_FOREACH(dt, t->dnssec_transactions, i)
+ if (DNS_TRANSACTION_IS_LIVE(dt->state))
+ return true;
+
+ return false;
+}
+
+static void dns_transaction_process_dnssec(DnsTransaction *t) {
+ int r;
+
+ assert(t);
+
+ /* Are there ongoing DNSSEC transactions? If so, let's wait for them. */
+ if (dns_transaction_dnssec_is_live(t))
+ return;
+
+ /* All our auxiliary DNSSEC transactions are complete now. Try
+ * to validate our RRset now. */
+ r = dns_transaction_validate_dnssec(t);
+ if (r < 0) {
+ dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
+ return;
+ }
+
+ if (t->answer_dnssec_result == DNSSEC_INCOMPATIBLE_SERVER &&
+ t->scope->dnssec_mode == DNSSEC_YES) {
+ /* We are not in automatic downgrade mode, and the
+ * server is bad, refuse operation. */
+ dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
+ return;
+ }
+
+ if (!IN_SET(t->answer_dnssec_result,
+ _DNSSEC_RESULT_INVALID, /* No DNSSEC validation enabled */
+ DNSSEC_VALIDATED, /* Answer is signed and validated successfully */
+ DNSSEC_UNSIGNED, /* Answer is right-fully unsigned */
+ DNSSEC_INCOMPATIBLE_SERVER)) { /* Server does not do DNSSEC (Yay, we are downgrade attack vulnerable!) */
+ dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
+ return;
+ }
+
+ dns_transaction_cache_answer(t);
+
+ if (t->answer_rcode == DNS_RCODE_SUCCESS)
+ dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
+ else
+ dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE);
}
void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
@@ -354,15 +577,20 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
assert(t);
assert(p);
- assert(t->state == DNS_TRANSACTION_PENDING);
assert(t->scope);
assert(t->scope->manager);
+ if (t->state != DNS_TRANSACTION_PENDING)
+ return;
+
/* Note that this call might invalidate the query. Callers
* should hence not attempt to access the query or transaction
* after calling this function. */
+ log_debug("Processing incoming packet on transaction %" PRIu16".", t->id);
+
switch (t->scope->protocol) {
+
case DNS_PROTOCOL_LLMNR:
assert(t->scope->link);
@@ -398,6 +626,11 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
break;
case DNS_PROTOCOL_DNS:
+ /* Note that we do not need to verify the
+ * addresses/port numbers of incoming traffic, as we
+ * invoked connect() on our UDP socket in which case
+ * the kernel already does the needed verification for
+ * us. */
break;
default:
@@ -428,34 +661,32 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0);
switch (t->scope->protocol) {
+
case DNS_PROTOCOL_DNS:
assert(t->server);
if (IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) {
- /* request failed, immediately try again with reduced features */
+ /* Request failed, immediately try again with reduced features */
log_debug("Server returned error: %s", dns_rcode_to_string(DNS_PACKET_RCODE(p)));
- dns_server_packet_failed(t->server, t->current_features);
-
- r = dns_transaction_go(t);
- if (r < 0) {
- dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
- return;
- }
-
+ dns_server_packet_failed(t->server, t->current_feature_level);
+ dns_transaction_retry(t);
return;
- } else
- dns_server_packet_received(t->server, t->current_features, ts - t->start_usec, p->size);
+ } else if (DNS_PACKET_TC(p))
+ dns_server_packet_truncated(t->server, t->current_feature_level);
+ else
+ dns_server_packet_received(t->server, p->ipproto, t->current_feature_level, ts - t->start_usec, p->size);
break;
+
case DNS_PROTOCOL_LLMNR:
case DNS_PROTOCOL_MDNS:
dns_scope_packet_received(t->scope, ts - t->start_usec);
-
break;
+
default:
- break;
+ assert_not_reached("Invalid DNS protocol.");
}
if (DNS_PACKET_TC(p)) {
@@ -466,6 +697,8 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
return;
}
+ log_debug("Reply truncated, retrying via TCP.");
+
/* Response was truncated, let's try again with good old TCP */
r = dns_transaction_open_tcp(t);
if (r == -ESRCH) {
@@ -473,37 +706,42 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS);
return;
}
+ if (r == -EOPNOTSUPP) {
+ /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */
+ dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED);
+ return;
+ }
if (r < 0) {
- /* On LLMNR and mDNS, if we cannot connect to the host,
+ /* On LLMNR, if we cannot connect to the host,
* we immediately give up */
- if (t->scope->protocol == DNS_PROTOCOL_LLMNR) {
+ if (t->scope->protocol != DNS_PROTOCOL_DNS) {
dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
return;
}
/* On DNS, couldn't send? Try immediately again, with a new server */
- dns_transaction_next_dns_server(t);
-
- r = dns_transaction_go(t);
- if (r < 0) {
- dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
- return;
- }
-
- return;
+ dns_transaction_retry(t);
}
+
+ return;
}
- /* Parse and update the cache */
+ /* Parse message, if it isn't parsed yet. */
r = dns_packet_extract(p);
if (r < 0) {
dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
return;
}
- if (t->scope->protocol == DNS_PROTOCOL_DNS) {
+ if (IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR)) {
+
/* Only consider responses with equivalent query section to the request */
- if (p->question->n_keys != 1 || dns_resource_key_equal(p->question->keys[0], t->key) <= 0) {
+ r = dns_packet_is_reply_for(p, t->key);
+ if (r < 0) {
+ dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
+ return;
+ }
+ if (r == 0) {
dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
return;
}
@@ -512,25 +750,39 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
dns_answer_unref(t->answer);
t->answer = dns_answer_ref(p->answer);
t->answer_rcode = DNS_PACKET_RCODE(p);
- t->answer_authenticated = t->scope->dnssec_mode == DNSSEC_TRUST && DNS_PACKET_AD(p);
-
- /* According to RFC 4795, section 2.9. only the RRs from the answer section shall be cached */
- if (DNS_PACKET_SHALL_CACHE(p))
- dns_cache_put(&t->scope->cache,
- t->key,
- DNS_PACKET_RCODE(p),
- p->answer,
- DNS_PACKET_ANCOUNT(p),
- t->answer_authenticated,
- 0,
- p->family,
- &p->sender);
- }
-
- if (DNS_PACKET_RCODE(p) == DNS_RCODE_SUCCESS)
- dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
- else
- dns_transaction_complete(t, DNS_TRANSACTION_FAILURE);
+ t->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
+ t->answer_authenticated = false;
+
+ /* Block GC while starting requests for additional DNSSEC RRs */
+ t->block_gc++;
+ r = dns_transaction_request_dnssec_keys(t);
+ t->block_gc--;
+
+ /* Maybe the transaction is ready for GC'ing now? If so, free it and return. */
+ if (!dns_transaction_gc(t))
+ return;
+
+ /* Requesting additional keys might have resulted in
+ * this transaction to fail, since the auxiliary
+ * request failed for some reason. If so, we are not
+ * in pending state anymore, and we should exit
+ * quickly. */
+ if (t->state != DNS_TRANSACTION_PENDING)
+ return;
+ if (r < 0) {
+ dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
+ return;
+ }
+ if (r > 0) {
+ /* There are DNSSEC transactions pending now. Update the state accordingly. */
+ t->state = DNS_TRANSACTION_VALIDATING;
+ dns_transaction_close_connection(t);
+ dns_transaction_stop_timeout(t);
+ return;
+ }
+ }
+
+ dns_transaction_process_dnssec(t);
}
static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
@@ -542,53 +794,96 @@ static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *use
assert(t->scope);
r = manager_recv(t->scope->manager, fd, DNS_PROTOCOL_DNS, &p);
- if (r <= 0)
- return r;
+ if (ERRNO_IS_DISCONNECT(-r)) {
+ usec_t usec;
- if (dns_packet_validate_reply(p) > 0 &&
- DNS_PACKET_ID(p) == t->id)
- dns_transaction_process_reply(t, p);
- else
- log_debug("Invalid DNS packet.");
+ /* UDP connection failure get reported via ICMP and then are possible delivered to us on the next
+ * recvmsg(). Treat this like a lost packet. */
+
+ log_debug_errno(r, "Connection failure for DNS UDP packet: %m");
+ assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &usec) >= 0);
+ dns_server_packet_lost(t->server, IPPROTO_UDP, t->current_feature_level, usec - t->start_usec);
+
+ dns_transaction_retry(t);
+ return 0;
+ }
+ if (r < 0) {
+ dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
+ return 0;
+ }
+
+ r = dns_packet_validate_reply(p);
+ if (r < 0) {
+ log_debug_errno(r, "Received invalid DNS packet as response, ignoring: %m");
+ return 0;
+ }
+ if (r == 0) {
+ log_debug("Received inappropriate DNS packet as response, ignoring: %m");
+ return 0;
+ }
+ if (DNS_PACKET_ID(p) != t->id) {
+ log_debug("Received packet with incorrect transaction ID, ignoring: %m");
+ return 0;
+ }
+
+ dns_transaction_process_reply(t, p);
return 0;
}
-static int dns_transaction_emit(DnsTransaction *t) {
+static int dns_transaction_emit_udp(DnsTransaction *t) {
int r;
assert(t);
- if (t->scope->protocol == DNS_PROTOCOL_DNS && !t->server) {
- DnsServer *server = NULL;
- _cleanup_close_ int fd = -1;
-
- fd = dns_scope_udp_dns_socket(t->scope, &server);
- if (fd < 0)
- return fd;
+ if (t->scope->protocol == DNS_PROTOCOL_DNS) {
- r = sd_event_add_io(t->scope->manager->event, &t->dns_udp_event_source, fd, EPOLLIN, on_dns_packet, t);
+ r = dns_transaction_pick_server(t);
if (r < 0)
return r;
- t->dns_udp_fd = fd;
- fd = -1;
- t->server = dns_server_ref(server);
- }
+ if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_UDP)
+ return -EAGAIN;
+
+ if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type))
+ return -EOPNOTSUPP;
+
+ if (r > 0 || t->dns_udp_fd < 0) { /* Server changed, or no connection yet. */
+ int fd;
+
+ dns_transaction_close_connection(t);
+
+ fd = dns_scope_socket_udp(t->scope, t->server, 53);
+ if (fd < 0)
+ return fd;
+
+ r = sd_event_add_io(t->scope->manager->event, &t->dns_udp_event_source, fd, EPOLLIN, on_dns_packet, t);
+ if (r < 0) {
+ safe_close(fd);
+ return r;
+ }
+
+ (void) sd_event_source_set_description(t->dns_udp_event_source, "dns-transaction-udp");
+ t->dns_udp_fd = fd;
+ }
+
+ r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level);
+ if (r < 0)
+ return r;
+ } else
+ dns_transaction_close_connection(t);
- r = dns_scope_emit(t->scope, t->dns_udp_fd, t->server, t->sent);
+ r = dns_scope_emit_udp(t->scope, t->dns_udp_fd, t->sent);
if (r < 0)
return r;
- if (t->server)
- t->current_features = t->server->possible_features;
+ dns_transaction_reset_answer(t);
return 0;
}
static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdata) {
DnsTransaction *t = userdata;
- int r;
assert(s);
assert(t);
@@ -596,17 +891,17 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat
if (!t->initial_jitter_scheduled || t->initial_jitter_elapsed) {
/* Timeout reached? Increase the timeout for the server used */
switch (t->scope->protocol) {
+
case DNS_PROTOCOL_DNS:
assert(t->server);
-
- dns_server_packet_lost(t->server, t->current_features, usec - t->start_usec);
-
+ dns_server_packet_lost(t->server, t->stream ? IPPROTO_TCP : IPPROTO_UDP, t->current_feature_level, usec - t->start_usec);
break;
+
case DNS_PROTOCOL_LLMNR:
case DNS_PROTOCOL_MDNS:
dns_scope_packet_lost(t->scope, usec - t->start_usec);
-
break;
+
default:
assert_not_reached("Invalid DNS protocol.");
}
@@ -615,13 +910,9 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat
t->initial_jitter_elapsed = true;
}
- /* ...and try again with a new server */
- dns_transaction_next_dns_server(t);
-
- r = dns_transaction_go(t);
- if (r < 0)
- dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
+ log_debug("Timeout reached on transaction %" PRIu16 ".", t->id);
+ dns_transaction_retry(t);
return 0;
}
@@ -630,36 +921,36 @@ static usec_t transaction_get_resend_timeout(DnsTransaction *t) {
assert(t->scope);
switch (t->scope->protocol) {
+
case DNS_PROTOCOL_DNS:
assert(t->server);
-
return t->server->resend_timeout;
+
case DNS_PROTOCOL_MDNS:
assert(t->n_attempts > 0);
return (1 << (t->n_attempts - 1)) * USEC_PER_SEC;
+
case DNS_PROTOCOL_LLMNR:
return t->scope->resend_timeout;
+
default:
assert_not_reached("Invalid DNS protocol.");
}
}
-static int dns_transaction_prepare_next_attempt(DnsTransaction *t, usec_t ts) {
- bool had_stream;
+static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
int r;
assert(t);
- had_stream = !!t->stream;
-
- dns_transaction_stop(t);
+ dns_transaction_stop_timeout(t);
if (t->n_attempts >= TRANSACTION_ATTEMPTS_MAX(t->scope->protocol)) {
dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED);
return 0;
}
- if (t->scope->protocol == DNS_PROTOCOL_LLMNR && had_stream) {
+ if (t->scope->protocol == DNS_PROTOCOL_LLMNR && t->tried_stream) {
/* If we already tried via a stream, then we don't
* retry on LLMNR. See RFC 4795, Section 2.7. */
dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED);
@@ -668,14 +959,12 @@ static int dns_transaction_prepare_next_attempt(DnsTransaction *t, usec_t ts) {
t->n_attempts++;
t->start_usec = ts;
- t->received = dns_packet_unref(t->received);
- t->answer = dns_answer_unref(t->answer);
- t->answer_rcode = 0;
- t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID;
+
+ dns_transaction_reset_answer(t);
/* Check the trust anchor. Do so only on classic DNS, since DNSSEC does not apply otherwise. */
if (t->scope->protocol == DNS_PROTOCOL_DNS) {
- r = dns_trust_anchor_lookup(&t->scope->manager->trust_anchor, t->key, &t->answer);
+ r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, t->key, &t->answer);
if (r < 0)
return r;
if (r > 0) {
@@ -685,11 +974,46 @@ static int dns_transaction_prepare_next_attempt(DnsTransaction *t, usec_t ts) {
dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
return 0;
}
+
+ if (dns_name_is_root(DNS_RESOURCE_KEY_NAME(t->key)) &&
+ t->key->type == DNS_TYPE_DS) {
+
+ /* Hmm, this is a request for the root DS? A
+ * DS RR doesn't exist in the root zone, and
+ * if our trust anchor didn't know it either,
+ * this means we cannot do any DNSSEC logic
+ * anymore. */
+
+ if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) {
+ /* We are in downgrade mode. In this
+ * case, synthesize an unsigned empty
+ * response, so that the any lookup
+ * depending on this one can continue
+ * assuming there was no DS, and hence
+ * the root zone was unsigned. */
+
+ t->answer_rcode = DNS_RCODE_SUCCESS;
+ t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR;
+ t->answer_authenticated = false;
+ dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
+ } else
+ /* If we are not in downgrade mode,
+ * then fail the lookup, because we
+ * cannot reasonably answer it. There
+ * might be DS RRs, but we don't know
+ * them, and the DNS server won't tell
+ * them to us (and even if it would,
+ * we couldn't validate it and trust
+ * it). */
+ dns_transaction_complete(t, DNS_TRANSACTION_NO_TRUST_ANCHOR);
+
+ return 0;
+ }
}
/* Check the zone, but only if this transaction is not used
* for probing or verifying a zone item. */
- if (set_isempty(t->zone_items)) {
+ if (set_isempty(t->notify_zone_items)) {
r = dns_zone_lookup(&t->scope->zone, t->key, &t->answer, NULL, NULL);
if (r < 0)
@@ -705,7 +1029,7 @@ static int dns_transaction_prepare_next_attempt(DnsTransaction *t, usec_t ts) {
/* Check the cache, but only if this transaction is not used
* for probing or verifying a zone item. */
- if (set_isempty(t->zone_items)) {
+ if (set_isempty(t->notify_zone_items)) {
/* Before trying the cache, let's make sure we figured out a
* server to use. Should this cause a change of server this
@@ -723,7 +1047,7 @@ static int dns_transaction_prepare_next_attempt(DnsTransaction *t, usec_t ts) {
if (t->answer_rcode == DNS_RCODE_SUCCESS)
dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS);
else
- dns_transaction_complete(t, DNS_TRANSACTION_FAILURE);
+ dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE);
return 0;
}
}
@@ -743,7 +1067,7 @@ static int dns_transaction_make_packet_mdns(DnsTransaction *t) {
assert(t);
assert(t->scope->protocol == DNS_PROTOCOL_MDNS);
- /* Discard any previously prepared packet, so we can start over and coaleasce again */
+ /* Discard any previously prepared packet, so we can start over and coalesce again */
t->sent = dns_packet_unref(t->sent);
r = dns_packet_new_query(&p, t->scope->protocol, 0, false);
@@ -794,7 +1118,7 @@ static int dns_transaction_make_packet_mdns(DnsTransaction *t) {
if (r < 0)
return r;
- r = dns_transaction_prepare_next_attempt(other, ts);
+ r = dns_transaction_prepare(other, ts);
if (r <= 0)
continue;
@@ -809,6 +1133,8 @@ static int dns_transaction_make_packet_mdns(DnsTransaction *t) {
if (r < 0)
return r;
+ (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout");
+
other->state = DNS_TRANSACTION_PENDING;
other->next_attempt_after = ts;
@@ -819,7 +1145,6 @@ static int dns_transaction_make_packet_mdns(DnsTransaction *t) {
}
DNS_PACKET_HEADER(p)->qdcount = htobe16(qdcount);
- DNS_PACKET_HEADER(p)->id = t->id;
/* Append known answer section if we're asking for any shared record */
if (add_known_answers) {
@@ -846,7 +1171,7 @@ static int dns_transaction_make_packet(DnsTransaction *t) {
if (t->sent)
return 0;
- r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode == DNSSEC_YES);
+ r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode != DNSSEC_NO);
if (r < 0)
return r;
@@ -876,11 +1201,14 @@ int dns_transaction_go(DnsTransaction *t) {
assert(t);
assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0);
- r = dns_transaction_prepare_next_attempt(t, ts);
+
+ r = dns_transaction_prepare(t, ts);
if (r <= 0)
return r;
- log_debug("Excercising transaction on scope %s on %s/%s",
+ log_debug("Excercising transaction %" PRIu16 " for <%s> on scope %s on %s/%s.",
+ t->id,
+ dns_transaction_key_string(t),
dns_protocol_to_string(t->scope->protocol),
t->scope->link ? t->scope->link->name : "*",
t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family));
@@ -898,10 +1226,12 @@ int dns_transaction_go(DnsTransaction *t) {
random_bytes(&jitter, sizeof(jitter));
switch (t->scope->protocol) {
+
case DNS_PROTOCOL_LLMNR:
jitter %= LLMNR_JITTER_INTERVAL_USEC;
accuracy = LLMNR_JITTER_INTERVAL_USEC;
break;
+
case DNS_PROTOCOL_MDNS:
jitter %= MDNS_JITTER_RANGE_USEC;
jitter += MDNS_JITTER_MIN_USEC;
@@ -920,6 +1250,8 @@ int dns_transaction_go(DnsTransaction *t) {
if (r < 0)
return r;
+ (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout");
+
t->n_attempts = 0;
t->next_attempt_after = ts;
t->state = DNS_TRANSACTION_PENDING;
@@ -950,7 +1282,11 @@ int dns_transaction_go(DnsTransaction *t) {
} else {
/* Try via UDP, and if that fails due to large size or lack of
* support try via TCP */
- r = dns_transaction_emit(t);
+ r = dns_transaction_emit_udp(t);
+ if (r == -EMSGSIZE)
+ log_debug("Sending query via TCP since it is too large.");
+ if (r == -EAGAIN)
+ log_debug("Sending query via TCP since server doesn't support UDP.");
if (r == -EMSGSIZE || r == -EAGAIN)
r = dns_transaction_open_tcp(t);
}
@@ -959,14 +1295,20 @@ int dns_transaction_go(DnsTransaction *t) {
/* No servers to send this to? */
dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS);
return 0;
- } else if (r < 0) {
+ }
+ if (r == -EOPNOTSUPP) {
+ /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */
+ dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED);
+ return 0;
+ }
+ if (r < 0) {
if (t->scope->protocol != DNS_PROTOCOL_DNS) {
dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
return 0;
}
/* Couldn't send? Try immediately again, with a new server */
- dns_transaction_next_dns_server(t);
+ dns_scope_next_dns_server(t->scope);
return dns_transaction_go(t);
}
@@ -982,16 +1324,1502 @@ int dns_transaction_go(DnsTransaction *t) {
if (r < 0)
return r;
+ (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout");
+
t->state = DNS_TRANSACTION_PENDING;
t->next_attempt_after = ts;
return 1;
}
+static int dns_transaction_find_cyclic(DnsTransaction *t, DnsTransaction *aux) {
+ DnsTransaction *n;
+ Iterator i;
+ int r;
+
+ assert(t);
+ assert(aux);
+
+ /* Try to find cyclic dependencies between transaction objects */
+
+ if (t == aux)
+ return 1;
+
+ SET_FOREACH(n, aux->dnssec_transactions, i) {
+ r = dns_transaction_find_cyclic(t, n);
+ if (r != 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResourceKey *key, DnsTransaction **ret) {
+ DnsTransaction *aux;
+ int r;
+
+ assert(t);
+ assert(ret);
+ assert(key);
+
+ aux = dns_scope_find_transaction(t->scope, key, true);
+ if (!aux) {
+ r = dns_transaction_new(&aux, t->scope, key);
+ if (r < 0)
+ return r;
+ } else {
+ if (set_contains(t->dnssec_transactions, aux)) {
+ *ret = aux;
+ return 0;
+ }
+
+ r = dns_transaction_find_cyclic(t, aux);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ log_debug("Detected potential cyclic dependency, refusing to add transaction %" PRIu16 " (%s) as dependency for %" PRIu16 " (%s).",
+ aux->id,
+ strna(dns_transaction_key_string(aux)),
+ t->id,
+ strna(dns_transaction_key_string(t)));
+ return -ELOOP;
+ }
+ }
+
+ r = set_ensure_allocated(&t->dnssec_transactions, NULL);
+ if (r < 0)
+ goto gc;
+
+ r = set_ensure_allocated(&aux->notify_transactions, NULL);
+ if (r < 0)
+ goto gc;
+
+ r = set_put(t->dnssec_transactions, aux);
+ if (r < 0)
+ goto gc;
+
+ r = set_put(aux->notify_transactions, t);
+ if (r < 0) {
+ (void) set_remove(t->dnssec_transactions, aux);
+ goto gc;
+ }
+
+ *ret = aux;
+ return 1;
+
+gc:
+ dns_transaction_gc(aux);
+ return r;
+}
+
+static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey *key) {
+ _cleanup_(dns_answer_unrefp) DnsAnswer *a = NULL;
+ DnsTransaction *aux;
+ int r;
+
+ assert(t);
+ assert(key);
+
+ /* Try to get the data from the trust anchor */
+ r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, key, &a);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ r = dns_answer_extend(&t->validated_keys, a);
+ if (r < 0)
+ return r;
+
+ return 0;
+ }
+
+ /* This didn't work, ask for it via the network/cache then. */
+ r = dns_transaction_add_dnssec_transaction(t, key, &aux);
+ if (r == -ELOOP) /* This would result in a cyclic dependency */
+ return 0;
+ if (r < 0)
+ return r;
+
+ if (aux->state == DNS_TRANSACTION_NULL) {
+ r = dns_transaction_go(aux);
+ if (r < 0)
+ return r;
+ }
+
+ return 1;
+}
+
+static int dns_transaction_has_positive_answer(DnsTransaction *t, DnsAnswerFlags *flags) {
+ int r;
+
+ assert(t);
+
+ /* Checks whether the answer is positive, i.e. either a direct
+ * answer to the question, or a CNAME/DNAME for it */
+
+ r = dns_answer_match_key(t->answer, t->key, flags);
+ if (r != 0)
+ return r;
+
+ r = dns_answer_find_cname_or_dname(t->answer, t->key, NULL, flags);
+ if (r != 0)
+ return r;
+
+ return false;
+}
+
+static int dns_transaction_negative_trust_anchor_lookup(DnsTransaction *t, const char *name) {
+ int r;
+
+ assert(t);
+
+ /* Check whether the specified name is in the the NTA
+ * database, either in the global one, or the link-local
+ * one. */
+
+ r = dns_trust_anchor_lookup_negative(&t->scope->manager->trust_anchor, name);
+ if (r != 0)
+ return r;
+
+ if (!t->scope->link)
+ return 0;
+
+ return set_contains(t->scope->link->dnssec_negative_trust_anchors, name);
+}
+
+static int dns_transaction_has_unsigned_negative_answer(DnsTransaction *t) {
+ int r;
+
+ assert(t);
+
+ /* Checks whether the answer is negative, and lacks NSEC/NSEC3
+ * RRs to prove it */
+
+ r = dns_transaction_has_positive_answer(t, NULL);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return false;
+
+ /* Is this key explicitly listed as a negative trust anchor?
+ * If so, it's nothing we need to care about */
+ r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(t->key));
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return false;
+
+ /* The answer does not contain any RRs that match to the
+ * question. If so, let's see if there are any NSEC/NSEC3 RRs
+ * included. If not, the answer is unsigned. */
+
+ r = dns_answer_contains_nsec_or_nsec3(t->answer);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return false;
+
+ return true;
+}
+
+static int dns_transaction_is_primary_response(DnsTransaction *t, DnsResourceRecord *rr) {
+ int r;
+
+ assert(t);
+ assert(rr);
+
+ /* Check if the specified RR is the "primary" response,
+ * i.e. either matches the question precisely or is a
+ * CNAME/DNAME for it, or is any kind of NSEC/NSEC3 RR */
+
+ r = dns_resource_key_match_rr(t->key, rr, NULL);
+ if (r != 0)
+ return r;
+
+ r = dns_resource_key_match_cname_or_dname(t->key, rr->key, NULL);
+ if (r != 0)
+ return r;
+
+ if (rr->key->type == DNS_TYPE_NSEC3) {
+ const char *p;
+
+ p = DNS_RESOURCE_KEY_NAME(rr->key);
+ r = dns_name_parent(&p);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), p);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return true;
+ }
+ }
+
+ return rr->key->type == DNS_TYPE_NSEC;
+}
+
+static bool dns_transaction_dnssec_supported(DnsTransaction *t) {
+ assert(t);
+
+ /* Checks whether our transaction's DNS server is assumed to be compatible with DNSSEC. Returns false as soon
+ * as we changed our mind about a server, and now believe it is incompatible with DNSSEC. */
+
+ if (t->scope->protocol != DNS_PROTOCOL_DNS)
+ return false;
+
+ /* If we have picked no server, then we are working from the cache or some other source, and DNSSEC might well
+ * be supported, hence return true. */
+ if (!t->server)
+ return true;
+
+ if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_DO)
+ return false;
+
+ return dns_server_dnssec_supported(t->server);
+}
+
+static bool dns_transaction_dnssec_supported_full(DnsTransaction *t) {
+ DnsTransaction *dt;
+ Iterator i;
+
+ assert(t);
+
+ /* Checks whether our transaction our any of the auxiliary transactions couldn't do DNSSEC. */
+
+ if (!dns_transaction_dnssec_supported(t))
+ return false;
+
+ SET_FOREACH(dt, t->dnssec_transactions, i)
+ if (!dns_transaction_dnssec_supported(dt))
+ return false;
+
+ return true;
+}
+
+int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
+ DnsResourceRecord *rr;
+
+ int r;
+
+ assert(t);
+
+ /*
+ * Retrieve all auxiliary RRs for the answer we got, so that
+ * we can verify signatures or prove that RRs are rightfully
+ * unsigned. Specifically:
+ *
+ * - For RRSIG we get the matching DNSKEY
+ * - For DNSKEY we get the matching DS
+ * - For unsigned SOA/NS we get the matching DS
+ * - For unsigned CNAME/DNAME/DS we get the parent SOA RR
+ * - For other unsigned RRs we get the matching SOA RR
+ * - For SOA/NS/DS queries with no matching response RRs, and no NSEC/NSEC3, the parent's SOA RR
+ * - For other queries with no matching response RRs, and no NSEC/NSEC3, the SOA RR
+ */
+
+ if (t->scope->dnssec_mode == DNSSEC_NO)
+ return 0;
+ if (t->answer_source != DNS_TRANSACTION_NETWORK)
+ return 0; /* We only need to validate stuff from the network */
+ if (!dns_transaction_dnssec_supported(t))
+ return 0; /* If we can't do DNSSEC anyway there's no point in geting the auxiliary RRs */
+
+ DNS_ANSWER_FOREACH(rr, t->answer) {
+
+ if (dns_type_is_pseudo(rr->key->type))
+ continue;
+
+ /* If this RR is in the negative trust anchor, we don't need to validate it. */
+ r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(rr->key));
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
+
+ switch (rr->key->type) {
+
+ case DNS_TYPE_RRSIG: {
+ /* For each RRSIG we request the matching DNSKEY */
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *dnskey = NULL;
+
+ /* If this RRSIG is about a DNSKEY RR and the
+ * signer is the same as the owner, then we
+ * already have the DNSKEY, and we don't have
+ * to look for more. */
+ if (rr->rrsig.type_covered == DNS_TYPE_DNSKEY) {
+ r = dns_name_equal(rr->rrsig.signer, DNS_RESOURCE_KEY_NAME(rr->key));
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
+ }
+
+ /* If the signer is not a parent of our
+ * original query, then this is about an
+ * auxiliary RRset, but not anything we asked
+ * for. In this case we aren't interested,
+ * because we don't want to request additional
+ * RRs for stuff we didn't really ask for, and
+ * also to avoid request loops, where
+ * additional RRs from one transaction result
+ * in another transaction whose additonal RRs
+ * point back to the original transaction, and
+ * we deadlock. */
+ r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), rr->rrsig.signer);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ dnskey = dns_resource_key_new(rr->key->class, DNS_TYPE_DNSKEY, rr->rrsig.signer);
+ if (!dnskey)
+ return -ENOMEM;
+
+ log_debug("Requesting DNSKEY to validate transaction %" PRIu16" (%s, RRSIG with key tag: %" PRIu16 ").", t->id, DNS_RESOURCE_KEY_NAME(rr->key), rr->rrsig.key_tag);
+ r = dns_transaction_request_dnssec_rr(t, dnskey);
+ if (r < 0)
+ return r;
+ break;
+ }
+
+ case DNS_TYPE_DNSKEY: {
+ /* For each DNSKEY we request the matching DS */
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL;
+
+ /* If the DNSKEY we are looking at is not for
+ * zone we are interested in, nor any of its
+ * parents, we aren't interested, and don't
+ * request it. After all, we don't want to end
+ * up in request loops, and want to keep
+ * additional traffic down. */
+
+ r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), DNS_RESOURCE_KEY_NAME(rr->key));
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(rr->key));
+ if (!ds)
+ return -ENOMEM;
+
+ log_debug("Requesting DS to validate transaction %" PRIu16" (%s, DNSKEY with key tag: %" PRIu16 ").", t->id, DNS_RESOURCE_KEY_NAME(rr->key), dnssec_keytag(rr, false));
+ r = dns_transaction_request_dnssec_rr(t, ds);
+ if (r < 0)
+ return r;
+
+ break;
+ }
+
+ case DNS_TYPE_SOA:
+ case DNS_TYPE_NS: {
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL;
+
+ /* For an unsigned SOA or NS, try to acquire
+ * the matching DS RR, as we are at a zone cut
+ * then, and whether a DS exists tells us
+ * whether the zone is signed. Do so only if
+ * this RR matches our original question,
+ * however. */
+
+ r = dns_resource_key_match_rr(t->key, rr, NULL);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ r = dnssec_has_rrsig(t->answer, rr->key);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
+
+ ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(rr->key));
+ if (!ds)
+ return -ENOMEM;
+
+ log_debug("Requesting DS to validate transaction %" PRIu16 " (%s, unsigned SOA/NS RRset).", t->id, DNS_RESOURCE_KEY_NAME(rr->key));
+ r = dns_transaction_request_dnssec_rr(t, ds);
+ if (r < 0)
+ return r;
+
+ break;
+ }
+
+ case DNS_TYPE_DS:
+ case DNS_TYPE_CNAME:
+ case DNS_TYPE_DNAME: {
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL;
+ const char *name;
+
+ /* CNAMEs and DNAMEs cannot be located at a
+ * zone apex, hence ask for the parent SOA for
+ * unsigned CNAME/DNAME RRs, maybe that's the
+ * apex. But do all that only if this is
+ * actually a response to our original
+ * question.
+ *
+ * Similar for DS RRs, which are signed when
+ * the parent SOA is signed. */
+
+ r = dns_transaction_is_primary_response(t, rr);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ r = dnssec_has_rrsig(t->answer, rr->key);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
+
+ name = DNS_RESOURCE_KEY_NAME(rr->key);
+ r = dns_name_parent(&name);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, name);
+ if (!soa)
+ return -ENOMEM;
+
+ log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned CNAME/DNAME/DS RRset).", t->id, DNS_RESOURCE_KEY_NAME(rr->key));
+ r = dns_transaction_request_dnssec_rr(t, soa);
+ if (r < 0)
+ return r;
+
+ break;
+ }
+
+ default: {
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL;
+
+ /* For other unsigned RRsets (including
+ * NSEC/NSEC3!), look for proof the zone is
+ * unsigned, by requesting the SOA RR of the
+ * zone. However, do so only if they are
+ * directly relevant to our original
+ * question. */
+
+ r = dns_transaction_is_primary_response(t, rr);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ r = dnssec_has_rrsig(t->answer, rr->key);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
+
+ soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, DNS_RESOURCE_KEY_NAME(rr->key));
+ if (!soa)
+ return -ENOMEM;
+
+ log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned non-SOA/NS RRset <%s>).", t->id, DNS_RESOURCE_KEY_NAME(rr->key), dns_resource_record_to_string(rr));
+ r = dns_transaction_request_dnssec_rr(t, soa);
+ if (r < 0)
+ return r;
+ break;
+ }}
+ }
+
+ /* Above, we requested everything necessary to validate what
+ * we got. Now, let's request what we need to validate what we
+ * didn't get... */
+
+ r = dns_transaction_has_unsigned_negative_answer(t);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ const char *name;
+
+ name = DNS_RESOURCE_KEY_NAME(t->key);
+
+ /* If this was a SOA or NS request, then this
+ * indicates that we are not at a zone apex, hence ask
+ * the parent name instead. If this was a DS request,
+ * then it's signed when the parent zone is signed,
+ * hence ask the parent in that case, too. */
+
+ if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS, DNS_TYPE_DS)) {
+ r = dns_name_parent(&name);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned empty SOA/NS/DS response).", t->id, DNS_RESOURCE_KEY_NAME(t->key));
+ else
+ name = NULL;
+ } else
+ log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned empty non-SOA/NS/DS response).", t->id, DNS_RESOURCE_KEY_NAME(t->key));
+
+ if (name) {
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL;
+
+ soa = dns_resource_key_new(t->key->class, DNS_TYPE_SOA, name);
+ if (!soa)
+ return -ENOMEM;
+
+ r = dns_transaction_request_dnssec_rr(t, soa);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ return dns_transaction_dnssec_is_live(t);
+}
+
+void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source) {
+ int r;
+
+ assert(t);
+ assert(source);
+
+ if (!IN_SET(t->state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING))
+ return;
+
+ /* Invoked whenever any of our auxiliary DNSSEC transactions
+ completed its work. We copy any RRs from that transaction
+ over into our list of validated keys -- but only if the
+ answer is authenticated.
+
+ Note that we fail our transaction if the auxiliary
+ transaction failed, except on NXDOMAIN. This is because
+ some broken DNS servers (Akamai...) will return NXDOMAIN
+ for empty non-terminals. */
+
+ switch (source->state) {
+
+ case DNS_TRANSACTION_DNSSEC_FAILED:
+
+ log_debug("Auxiliary DNSSEC RR query failed validation: %s", dnssec_result_to_string(source->answer_dnssec_result));
+ t->answer_dnssec_result = source->answer_dnssec_result; /* Copy error code over */
+ dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
+ break;
+
+ case DNS_TRANSACTION_RCODE_FAILURE:
+
+ if (source->answer_rcode != DNS_RCODE_NXDOMAIN) {
+ log_debug("Auxiliary DNSSEC RR query failed with rcode=%i.", source->answer_rcode);
+ goto fail;
+ }
+
+ /* fall-through: NXDOMAIN is good enough for us */
+
+ case DNS_TRANSACTION_SUCCESS:
+ if (source->answer_authenticated) {
+ r = dns_answer_extend(&t->validated_keys, source->answer);
+ if (r < 0) {
+ log_error_errno(r, "Failed to merge validated DNSSEC key data: %m");
+ goto fail;
+ }
+ }
+
+ /* If the state is still PENDING, we are still in the loop
+ * that adds further DNSSEC transactions, hence don't check if
+ * we are ready yet. If the state is VALIDATING however, we
+ * should check if we are complete now. */
+ if (t->state == DNS_TRANSACTION_VALIDATING)
+ dns_transaction_process_dnssec(t);
+ break;
+
+ default:
+ log_debug("Auxiliary DNSSEC RR query failed with %s", dns_transaction_state_to_string(source->state));
+ goto fail;
+ }
+
+ return;
+
+fail:
+ t->answer_dnssec_result = DNSSEC_FAILED_AUXILIARY;
+ dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
+}
+
+static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) {
+ DnsResourceRecord *rr;
+ int ifindex, r;
+
+ assert(t);
+
+ /* Add all DNSKEY RRs from the answer that are validated by DS
+ * RRs from the list of validated keys to the list of
+ * validated keys. */
+
+ DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, t->answer) {
+
+ r = dnssec_verify_dnskey_search(rr, t->validated_keys);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ /* If so, the DNSKEY is validated too. */
+ r = dns_answer_add_extend(&t->validated_keys, rr, ifindex, DNS_ANSWER_AUTHENTICATED);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *rr) {
+ int r;
+
+ assert(t);
+ assert(rr);
+
+ /* Checks if the RR we are looking for must be signed with an
+ * RRSIG. This is used for positive responses. */
+
+ if (t->scope->dnssec_mode == DNSSEC_NO)
+ return false;
+
+ if (dns_type_is_pseudo(rr->key->type))
+ return -EINVAL;
+
+ r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(rr->key));
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return false;
+
+ switch (rr->key->type) {
+
+ case DNS_TYPE_RRSIG:
+ /* RRSIGs are the signatures themselves, they need no signing. */
+ return false;
+
+ case DNS_TYPE_SOA:
+ case DNS_TYPE_NS: {
+ DnsTransaction *dt;
+ Iterator i;
+
+ /* For SOA or NS RRs we look for a matching DS transaction */
+
+ SET_FOREACH(dt, t->dnssec_transactions, i) {
+
+ if (dt->key->class != rr->key->class)
+ continue;
+ if (dt->key->type != DNS_TYPE_DS)
+ continue;
+
+ r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), DNS_RESOURCE_KEY_NAME(rr->key));
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ /* We found a DS transactions for the SOA/NS
+ * RRs we are looking at. If it discovered signed DS
+ * RRs, then we need to be signed, too. */
+
+ if (!dt->answer_authenticated)
+ return false;
+
+ return dns_answer_match_key(dt->answer, dt->key, NULL);
+ }
+
+ /* We found nothing that proves this is safe to leave
+ * this unauthenticated, hence ask inist on
+ * authentication. */
+ return true;
+ }
+
+ case DNS_TYPE_DS:
+ case DNS_TYPE_CNAME:
+ case DNS_TYPE_DNAME: {
+ const char *parent = NULL;
+ DnsTransaction *dt;
+ Iterator i;
+
+ /*
+ * CNAME/DNAME RRs cannot be located at a zone apex, hence look directly for the parent SOA.
+ *
+ * DS RRs are signed if the parent is signed, hence also look at the parent SOA
+ */
+
+ SET_FOREACH(dt, t->dnssec_transactions, i) {
+
+ if (dt->key->class != rr->key->class)
+ continue;
+ if (dt->key->type != DNS_TYPE_SOA)
+ continue;
+
+ if (!parent) {
+ parent = DNS_RESOURCE_KEY_NAME(rr->key);
+ r = dns_name_parent(&parent);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ if (rr->key->type == DNS_TYPE_DS)
+ return true;
+
+ /* A CNAME/DNAME without a parent? That's sooo weird. */
+ log_debug("Transaction %" PRIu16 " claims CNAME/DNAME at root. Refusing.", t->id);
+ return -EBADMSG;
+ }
+ }
+
+ r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), parent);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ return t->answer_authenticated;
+ }
+
+ return true;
+ }
+
+ default: {
+ DnsTransaction *dt;
+ Iterator i;
+
+ /* Any other kind of RR (including DNSKEY/NSEC/NSEC3). Let's see if our SOA lookup was authenticated */
+
+ SET_FOREACH(dt, t->dnssec_transactions, i) {
+
+ if (dt->key->class != rr->key->class)
+ continue;
+ if (dt->key->type != DNS_TYPE_SOA)
+ continue;
+
+ r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), DNS_RESOURCE_KEY_NAME(rr->key));
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ /* We found the transaction that was supposed to find
+ * the SOA RR for us. It was successful, but found no
+ * RR for us. This means we are not at a zone cut. In
+ * this case, we require authentication if the SOA
+ * lookup was authenticated too. */
+ return t->answer_authenticated;
+ }
+
+ return true;
+ }}
+}
+
+static int dns_transaction_in_private_tld(DnsTransaction *t, const DnsResourceKey *key) {
+ DnsTransaction *dt;
+ const char *tld;
+ Iterator i;
+ int r;
+
+ /* If DNSSEC downgrade mode is on, checks whether the
+ * specified RR is one level below a TLD we have proven not to
+ * exist. In such a case we assume that this is a private
+ * domain, and permit it.
+ *
+ * This detects cases like the Fritz!Box router networks. Each
+ * Fritz!Box router serves a private "fritz.box" zone, in the
+ * non-existing TLD "box". Requests for the "fritz.box" domain
+ * are served by the router itself, while requests for the
+ * "box" domain will result in NXDOMAIN.
+ *
+ * Note that this logic is unable to detect cases where a
+ * router serves a private DNS zone directly under
+ * non-existing TLD. In such a case we cannot detect whether
+ * the TLD is supposed to exist or not, as all requests we
+ * make for it will be answered by the router's zone, and not
+ * by the root zone. */
+
+ assert(t);
+
+ if (t->scope->dnssec_mode != DNSSEC_ALLOW_DOWNGRADE)
+ return false; /* In strict DNSSEC mode what doesn't exist, doesn't exist */
+
+ tld = DNS_RESOURCE_KEY_NAME(key);
+ r = dns_name_parent(&tld);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return false; /* Already the root domain */
+
+ if (!dns_name_is_single_label(tld))
+ return false;
+
+ SET_FOREACH(dt, t->dnssec_transactions, i) {
+
+ if (dt->key->class != key->class)
+ continue;
+
+ r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), tld);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ /* We found an auxiliary lookup we did for the TLD. If
+ * that returned with NXDOMAIN, we know the TLD didn't
+ * exist, and hence this might be a private zone. */
+
+ return dt->answer_rcode == DNS_RCODE_NXDOMAIN;
+ }
+
+ return false;
+}
+
+static int dns_transaction_requires_nsec(DnsTransaction *t) {
+ DnsTransaction *dt;
+ const char *name;
+ Iterator i;
+ int r;
+
+ assert(t);
+
+ /* Checks if we need to insist on NSEC/NSEC3 RRs for proving
+ * this negative reply */
+
+ if (t->scope->dnssec_mode == DNSSEC_NO)
+ return false;
+
+ if (dns_type_is_pseudo(t->key->type))
+ return -EINVAL;
+
+ r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(t->key));
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return false;
+
+ r = dns_transaction_in_private_tld(t, t->key);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ /* The lookup is from a TLD that is proven not to
+ * exist, and we are in downgrade mode, hence ignore
+ * that fact that we didn't get any NSEC RRs.*/
+
+ log_info("Detected a negative query %s in a private DNS zone, permitting unsigned response.", dns_transaction_key_string(t));
+ return false;
+ }
+
+ name = DNS_RESOURCE_KEY_NAME(t->key);
+
+ if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS, DNS_TYPE_DS)) {
+
+ /* We got a negative reply for this SOA/NS lookup? If
+ * so, then we are not at a zone apex, and thus should
+ * look at the result of the parent SOA lookup.
+ *
+ * We got a negative reply for this DS lookup? DS RRs
+ * are signed when their parent zone is signed, hence
+ * also check the parent SOA in this case. */
+
+ r = dns_name_parent(&name);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return true;
+ }
+
+ /* For all other RRs we check the SOA on the same level to see
+ * if it's signed. */
+
+ SET_FOREACH(dt, t->dnssec_transactions, i) {
+
+ if (dt->key->class != t->key->class)
+ continue;
+ if (dt->key->type != DNS_TYPE_SOA)
+ continue;
+
+ r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), name);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ return dt->answer_authenticated;
+ }
+
+ /* If in doubt, require NSEC/NSEC3 */
+ return true;
+}
+
+static int dns_transaction_dnskey_authenticated(DnsTransaction *t, DnsResourceRecord *rr) {
+ DnsResourceRecord *rrsig;
+ bool found = false;
+ int r;
+
+ /* Checks whether any of the DNSKEYs used for the RRSIGs for
+ * the specified RRset is authenticated (i.e. has a matching
+ * DS RR). */
+
+ r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(rr->key));
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return false;
+
+ DNS_ANSWER_FOREACH(rrsig, t->answer) {
+ DnsTransaction *dt;
+ Iterator i;
+
+ r = dnssec_key_match_rrsig(rr->key, rrsig);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ SET_FOREACH(dt, t->dnssec_transactions, i) {
+
+ if (dt->key->class != rr->key->class)
+ continue;
+
+ if (dt->key->type == DNS_TYPE_DNSKEY) {
+
+ r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), rrsig->rrsig.signer);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ /* OK, we found an auxiliary DNSKEY
+ * lookup. If that lookup is
+ * authenticated, report this. */
+
+ if (dt->answer_authenticated)
+ return true;
+
+ found = true;
+
+ } else if (dt->key->type == DNS_TYPE_DS) {
+
+ r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), rrsig->rrsig.signer);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ /* OK, we found an auxiliary DS
+ * lookup. If that lookup is
+ * authenticated and non-zero, we
+ * won! */
+
+ if (!dt->answer_authenticated)
+ return false;
+
+ return dns_answer_match_key(dt->answer, dt->key, NULL);
+ }
+ }
+ }
+
+ return found ? false : -ENXIO;
+}
+
+static int dns_transaction_known_signed(DnsTransaction *t, DnsResourceRecord *rr) {
+ assert(t);
+ assert(rr);
+
+ /* We know that the root domain is signed, hence if it appears
+ * not to be signed, there's a problem with the DNS server */
+
+ return rr->key->class == DNS_CLASS_IN &&
+ dns_name_is_root(DNS_RESOURCE_KEY_NAME(rr->key));
+}
+
+static int dns_transaction_check_revoked_trust_anchors(DnsTransaction *t) {
+ DnsResourceRecord *rr;
+ int r;
+
+ assert(t);
+
+ /* Maybe warn the user that we encountered a revoked DNSKEY
+ * for a key from our trust anchor. Note that we don't care
+ * whether the DNSKEY can be authenticated or not. It's
+ * sufficient if it is self-signed. */
+
+ DNS_ANSWER_FOREACH(rr, t->answer) {
+ r = dns_trust_anchor_check_revoked(&t->scope->manager->trust_anchor, rr, t->answer);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int dns_transaction_invalidate_revoked_keys(DnsTransaction *t) {
+ bool changed;
+ int r;
+
+ assert(t);
+
+ /* Removes all DNSKEY/DS objects from t->validated_keys that
+ * our trust anchors database considers revoked. */
+
+ do {
+ DnsResourceRecord *rr;
+
+ changed = false;
+
+ DNS_ANSWER_FOREACH(rr, t->validated_keys) {
+ r = dns_trust_anchor_is_revoked(&t->scope->manager->trust_anchor, rr);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ r = dns_answer_remove_by_rr(&t->validated_keys, rr);
+ if (r < 0)
+ return r;
+
+ assert(r > 0);
+ changed = true;
+ break;
+ }
+ }
+ } while (changed);
+
+ return 0;
+}
+
+int dns_transaction_validate_dnssec(DnsTransaction *t) {
+ _cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL;
+ enum {
+ PHASE_DNSKEY, /* Phase #1, only validate DNSKEYs */
+ PHASE_NSEC, /* Phase #2, only validate NSEC+NSEC3 */
+ PHASE_ALL, /* Phase #3, validate everything else */
+ } phase;
+ DnsResourceRecord *rr;
+ DnsAnswerFlags flags;
+ int r;
+
+ assert(t);
+
+ /* We have now collected all DS and DNSKEY RRs in
+ * t->validated_keys, let's see which RRs we can now
+ * authenticate with that. */
+
+ if (t->scope->dnssec_mode == DNSSEC_NO)
+ return 0;
+
+ /* Already validated */
+ if (t->answer_dnssec_result != _DNSSEC_RESULT_INVALID)
+ return 0;
+
+ /* Our own stuff needs no validation */
+ if (IN_SET(t->answer_source, DNS_TRANSACTION_ZONE, DNS_TRANSACTION_TRUST_ANCHOR)) {
+ t->answer_dnssec_result = DNSSEC_VALIDATED;
+ t->answer_authenticated = true;
+ return 0;
+ }
+
+ /* Cached stuff is not affected by validation. */
+ if (t->answer_source != DNS_TRANSACTION_NETWORK)
+ return 0;
+
+ if (!dns_transaction_dnssec_supported_full(t)) {
+ /* The server does not support DNSSEC, or doesn't augment responses with RRSIGs. */
+ t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER;
+ log_debug("Cannot validate response, server lacks DNSSEC support.");
+ return 0;
+ }
+
+ log_debug("Validating response from transaction %" PRIu16 " (%s).", t->id, dns_transaction_key_string(t));
+
+ /* First, see if this response contains any revoked trust
+ * anchors we care about */
+ r = dns_transaction_check_revoked_trust_anchors(t);
+ if (r < 0)
+ return r;
+
+ /* Second, see if there are DNSKEYs we already know a
+ * validated DS for. */
+ r = dns_transaction_validate_dnskey_by_ds(t);
+ if (r < 0)
+ return r;
+
+ /* Third, remove all DNSKEY and DS RRs again that our trust
+ * anchor says are revoked. After all we might have marked
+ * some keys revoked above, but they might still be lingering
+ * in our validated_keys list. */
+ r = dns_transaction_invalidate_revoked_keys(t);
+ if (r < 0)
+ return r;
+
+ phase = PHASE_DNSKEY;
+ for (;;) {
+ bool changed = false, have_nsec = false;
+
+ DNS_ANSWER_FOREACH(rr, t->answer) {
+ DnsResourceRecord *rrsig = NULL;
+ DnssecResult result;
+
+ switch (rr->key->type) {
+
+ case DNS_TYPE_RRSIG:
+ continue;
+
+ case DNS_TYPE_DNSKEY:
+ /* We validate DNSKEYs only in the DNSKEY and ALL phases */
+ if (phase == PHASE_NSEC)
+ continue;
+ break;
+
+ case DNS_TYPE_NSEC:
+ case DNS_TYPE_NSEC3:
+ have_nsec = true;
+
+ /* We validate NSEC/NSEC3 only in the NSEC and ALL phases */
+ if (phase == PHASE_DNSKEY)
+ continue;
+
+ break;
+
+ default:
+ /* We validate all other RRs only in the ALL phases */
+ if (phase != PHASE_ALL)
+ continue;
+
+ break;
+ }
+
+ r = dnssec_verify_rrset_search(t->answer, rr->key, t->validated_keys, USEC_INFINITY, &result, &rrsig);
+ if (r < 0)
+ return r;
+
+ log_debug("Looking at %s: %s", strna(dns_resource_record_to_string(rr)), dnssec_result_to_string(result));
+
+ if (result == DNSSEC_VALIDATED) {
+
+ if (rr->key->type == DNS_TYPE_DNSKEY) {
+ /* If we just validated a
+ * DNSKEY RRset, then let's
+ * add these keys to the set
+ * of validated keys for this
+ * transaction. */
+
+ r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key, DNS_ANSWER_AUTHENTICATED);
+ if (r < 0)
+ return r;
+
+ /* some of the DNSKEYs we just
+ * added might already have
+ * been revoked, remove them
+ * again in that case. */
+ r = dns_transaction_invalidate_revoked_keys(t);
+ if (r < 0)
+ return r;
+ }
+
+ /* Add the validated RRset to the new
+ * list of validated RRsets, and
+ * remove it from the unvalidated
+ * RRsets. We mark the RRset as
+ * authenticated and cacheable. */
+ r = dns_answer_move_by_key(&validated, &t->answer, rr->key, DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE);
+ if (r < 0)
+ return r;
+
+ t->scope->manager->n_dnssec_secure++;
+
+ /* Exit the loop, we dropped something from the answer, start from the beginning */
+ changed = true;
+ break;
+ }
+
+ /* If we haven't read all DNSKEYs yet a negative result of the validation is irrelevant, as
+ * there might be more DNSKEYs coming. Similar, if we haven't read all NSEC/NSEC3 RRs yet, we
+ * cannot do positive wildcard proofs yet, as those require the NSEC/NSEC3 RRs. */
+ if (phase != PHASE_ALL)
+ continue;
+
+ if (result == DNSSEC_VALIDATED_WILDCARD) {
+ bool authenticated = false;
+ const char *suffix;
+
+ /* This RRset validated, but as a wildcard. This means we need to proof via NSEC/NSEC3
+ * that no matching non-wildcard RR exists.
+ *
+ * See RFC 5155, Section 8.8 and RFC 4035, Section 5.3.4*/
+
+ r = dns_name_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rrsig->rrsig.labels, &suffix);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EBADMSG;
+
+ r = dns_name_parent(&suffix);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EBADMSG;
+
+ r = dnssec_nsec_test_between(validated, DNS_RESOURCE_KEY_NAME(rr->key), suffix, &authenticated);
+ if (r < 0)
+ return r;
+
+ /* Unless the NSEC proof showed that the key really doesn't exist something is off. */
+ if (r == 0)
+ result = DNSSEC_INVALID;
+ else {
+ r = dns_answer_move_by_key(&validated, &t->answer, rr->key, authenticated ? (DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE) : 0);
+ if (r < 0)
+ return r;
+
+ if (authenticated)
+ t->scope->manager->n_dnssec_secure++;
+ else
+ t->scope->manager->n_dnssec_insecure++;
+
+ /* Exit the loop, we dropped something from the answer, start from the beginning */
+ changed = true;
+ break;
+ }
+ }
+
+ if (result == DNSSEC_NO_SIGNATURE) {
+ r = dns_transaction_requires_rrsig(t, rr);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ /* Data does not require signing. In that case, just copy it over,
+ * but remember that this is by no means authenticated.*/
+ r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0);
+ if (r < 0)
+ return r;
+
+ t->scope->manager->n_dnssec_insecure++;
+ changed = true;
+ break;
+ }
+
+ r = dns_transaction_known_signed(t, rr);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ /* This is an RR we know has to be signed. If it isn't this means
+ * the server is not attaching RRSIGs, hence complain. */
+
+ dns_server_packet_rrsig_missing(t->server);
+
+ if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) {
+
+ /* Downgrading is OK? If so, just consider the information unsigned */
+
+ r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0);
+ if (r < 0)
+ return r;
+
+ t->scope->manager->n_dnssec_insecure++;
+ changed = true;
+ break;
+ }
+
+ /* Otherwise, fail */
+ t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER;
+ return 0;
+ }
+
+ r = dns_transaction_in_private_tld(t, rr->key);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ _cleanup_free_ char *s = NULL;
+
+ /* The data is from a TLD that is proven not to exist, and we are in downgrade
+ * mode, hence ignore the fact that this was not signed. */
+
+ (void) dns_resource_key_to_string(rr->key, &s);
+ log_info("Detected RRset %s is in a private DNS zone, permitting unsigned RRs.", strna(s ? strstrip(s) : NULL));
+
+ r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0);
+ if (r < 0)
+ return r;
+
+ t->scope->manager->n_dnssec_insecure++;
+ changed = true;
+ break;
+ }
+ }
+
+ if (IN_SET(result,
+ DNSSEC_MISSING_KEY,
+ DNSSEC_SIGNATURE_EXPIRED,
+ DNSSEC_UNSUPPORTED_ALGORITHM)) {
+
+ r = dns_transaction_dnskey_authenticated(t, rr);
+ if (r < 0 && r != -ENXIO)
+ return r;
+ if (r == 0) {
+ /* The DNSKEY transaction was not authenticated, this means there's
+ * no DS for this, which means it's OK if no keys are found for this signature. */
+
+ r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0);
+ if (r < 0)
+ return r;
+
+ t->scope->manager->n_dnssec_insecure++;
+ changed = true;
+ break;
+ }
+ }
+
+ if (IN_SET(result,
+ DNSSEC_INVALID,
+ DNSSEC_SIGNATURE_EXPIRED,
+ DNSSEC_NO_SIGNATURE))
+ t->scope->manager->n_dnssec_bogus++;
+ else /* DNSSEC_MISSING_KEY or DNSSEC_UNSUPPORTED_ALGORITHM */
+ t->scope->manager->n_dnssec_indeterminate++;
+
+ r = dns_transaction_is_primary_response(t, rr);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ /* This is a primary response
+ * to our question, and it
+ * failed validation. That's
+ * fatal. */
+ t->answer_dnssec_result = result;
+ return 0;
+ }
+
+ /* This is just some auxiliary
+ * data. Just remove the RRset and
+ * continue. */
+ r = dns_answer_remove_by_key(&t->answer, rr->key);
+ if (r < 0)
+ return r;
+
+ /* Exit the loop, we dropped something from the answer, start from the beginning */
+ changed = true;
+ break;
+ }
+
+ /* Restart the inner loop as long as we managed to achieve something */
+ if (changed)
+ continue;
+
+ if (phase == PHASE_DNSKEY && have_nsec) {
+ /* OK, we processed all DNSKEYs, and there are NSEC/NSEC3 RRs, look at those now. */
+ phase = PHASE_NSEC;
+ continue;
+ }
+
+ if (phase != PHASE_ALL) {
+ /* OK, we processed all DNSKEYs and NSEC/NSEC3 RRs, look at all the rest now. Note that in this
+ * third phase we start to remove RRs we couldn't validate. */
+ phase = PHASE_ALL;
+ continue;
+ }
+
+ /* We're done */
+ break;
+ }
+
+ dns_answer_unref(t->answer);
+ t->answer = validated;
+ validated = NULL;
+
+ /* At this point the answer only contains validated
+ * RRsets. Now, let's see if it actually answers the question
+ * we asked. If so, great! If it doesn't, then see if
+ * NSEC/NSEC3 can prove this. */
+ r = dns_transaction_has_positive_answer(t, &flags);
+ if (r > 0) {
+ /* Yes, it answers the question! */
+
+ if (flags & DNS_ANSWER_AUTHENTICATED) {
+ /* The answer is fully authenticated, yay. */
+ t->answer_dnssec_result = DNSSEC_VALIDATED;
+ t->answer_rcode = DNS_RCODE_SUCCESS;
+ t->answer_authenticated = true;
+ } else {
+ /* The answer is not fully authenticated. */
+ t->answer_dnssec_result = DNSSEC_UNSIGNED;
+ t->answer_authenticated = false;
+ }
+
+ } else if (r == 0) {
+ DnssecNsecResult nr;
+ bool authenticated = false;
+
+ /* Bummer! Let's check NSEC/NSEC3 */
+ r = dnssec_nsec_test(t->answer, t->key, &nr, &authenticated, &t->answer_nsec_ttl);
+ if (r < 0)
+ return r;
+
+ switch (nr) {
+
+ case DNSSEC_NSEC_NXDOMAIN:
+ /* NSEC proves the domain doesn't exist. Very good. */
+ log_debug("Proved NXDOMAIN via NSEC/NSEC3 for transaction %u (%s)", t->id, dns_transaction_key_string(t));
+ t->answer_dnssec_result = DNSSEC_VALIDATED;
+ t->answer_rcode = DNS_RCODE_NXDOMAIN;
+ t->answer_authenticated = authenticated;
+ break;
+
+ case DNSSEC_NSEC_NODATA:
+ /* NSEC proves that there's no data here, very good. */
+ log_debug("Proved NODATA via NSEC/NSEC3 for transaction %u (%s)", t->id, dns_transaction_key_string(t));
+ t->answer_dnssec_result = DNSSEC_VALIDATED;
+ t->answer_rcode = DNS_RCODE_SUCCESS;
+ t->answer_authenticated = authenticated;
+ break;
+
+ case DNSSEC_NSEC_OPTOUT:
+ /* NSEC3 says the data might not be signed */
+ log_debug("Data is NSEC3 opt-out via NSEC/NSEC3 for transaction %u (%s)", t->id, dns_transaction_key_string(t));
+ t->answer_dnssec_result = DNSSEC_UNSIGNED;
+ t->answer_authenticated = false;
+ break;
+
+ case DNSSEC_NSEC_NO_RR:
+ /* No NSEC data? Bummer! */
+
+ r = dns_transaction_requires_nsec(t);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ t->answer_dnssec_result = DNSSEC_NO_SIGNATURE;
+ else {
+ t->answer_dnssec_result = DNSSEC_UNSIGNED;
+ t->answer_authenticated = false;
+ }
+
+ break;
+
+ case DNSSEC_NSEC_UNSUPPORTED_ALGORITHM:
+ /* We don't know the NSEC3 algorithm used? */
+ t->answer_dnssec_result = DNSSEC_UNSUPPORTED_ALGORITHM;
+ break;
+
+ case DNSSEC_NSEC_FOUND:
+ case DNSSEC_NSEC_CNAME:
+ /* NSEC says it needs to be there, but we couldn't find it? Bummer! */
+ t->answer_dnssec_result = DNSSEC_NSEC_MISMATCH;
+ break;
+
+ default:
+ assert_not_reached("Unexpected NSEC result.");
+ }
+ }
+
+ return 1;
+}
+
+const char *dns_transaction_key_string(DnsTransaction *t) {
+ assert(t);
+
+ if (!t->key_string) {
+ if (dns_resource_key_to_string(t->key, &t->key_string) < 0)
+ return "n/a";
+ }
+
+ return strstrip(t->key_string);
+}
+
static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] = {
[DNS_TRANSACTION_NULL] = "null",
[DNS_TRANSACTION_PENDING] = "pending",
- [DNS_TRANSACTION_FAILURE] = "failure",
+ [DNS_TRANSACTION_VALIDATING] = "validating",
+ [DNS_TRANSACTION_RCODE_FAILURE] = "rcode-failure",
[DNS_TRANSACTION_SUCCESS] = "success",
[DNS_TRANSACTION_NO_SERVERS] = "no-servers",
[DNS_TRANSACTION_TIMEOUT] = "timeout",
@@ -999,6 +2827,9 @@ static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX]
[DNS_TRANSACTION_INVALID_REPLY] = "invalid-reply",
[DNS_TRANSACTION_RESOURCES] = "resources",
[DNS_TRANSACTION_ABORTED] = "aborted",
+ [DNS_TRANSACTION_DNSSEC_FAILED] = "dnssec-failed",
+ [DNS_TRANSACTION_NO_TRUST_ANCHOR] = "no-trust-anchor",
+ [DNS_TRANSACTION_RR_TYPE_UNSUPPORTED] = "rr-type-unsupported",
};
DEFINE_STRING_TABLE_LOOKUP(dns_transaction_state, DnsTransactionState);
diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h
index af08b20e44..76cf6e71db 100644
--- a/src/resolve/resolved-dns-transaction.h
+++ b/src/resolve/resolved-dns-transaction.h
@@ -28,7 +28,8 @@ typedef enum DnsTransactionSource DnsTransactionSource;
enum DnsTransactionState {
DNS_TRANSACTION_NULL,
DNS_TRANSACTION_PENDING,
- DNS_TRANSACTION_FAILURE,
+ DNS_TRANSACTION_VALIDATING,
+ DNS_TRANSACTION_RCODE_FAILURE,
DNS_TRANSACTION_SUCCESS,
DNS_TRANSACTION_NO_SERVERS,
DNS_TRANSACTION_TIMEOUT,
@@ -36,10 +37,15 @@ enum DnsTransactionState {
DNS_TRANSACTION_INVALID_REPLY,
DNS_TRANSACTION_RESOURCES,
DNS_TRANSACTION_ABORTED,
+ DNS_TRANSACTION_DNSSEC_FAILED,
+ DNS_TRANSACTION_NO_TRUST_ANCHOR,
+ DNS_TRANSACTION_RR_TYPE_UNSUPPORTED,
_DNS_TRANSACTION_STATE_MAX,
_DNS_TRANSACTION_STATE_INVALID = -1
};
+#define DNS_TRANSACTION_IS_LIVE(state) IN_SET((state), DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING)
+
enum DnsTransactionSource {
DNS_TRANSACTION_NETWORK,
DNS_TRANSACTION_CACHE,
@@ -58,45 +64,75 @@ struct DnsTransaction {
DnsScope *scope;
DnsResourceKey *key;
+ char *key_string;
DnsTransactionState state;
+
uint16_t id;
- bool initial_jitter_scheduled;
- bool initial_jitter_elapsed;
+ bool tried_stream:1;
+
+ bool initial_jitter_scheduled:1;
+ bool initial_jitter_elapsed:1;
DnsPacket *sent, *received;
DnsAnswer *answer;
int answer_rcode;
+ DnssecResult answer_dnssec_result;
DnsTransactionSource answer_source;
+ uint32_t answer_nsec_ttl;
+
+ /* Indicates whether the primary answer is authenticated,
+ * i.e. whether the RRs from answer which directly match the
+ * question are authenticated, or, if there are none, whether
+ * the NODATA or NXDOMAIN case is. It says nothing about
+ * additional RRs listed in the answer, however they have
+ * their own DNS_ANSWER_AUTHORIZED FLAGS. Note that this bit
+ * is defined different than the AD bit in DNS packets, as
+ * that covers more than just the actual primary answer. */
bool answer_authenticated;
+ /* Contains DNSKEY, DS, SOA RRs we already verified and need
+ * to authenticate this reply */
+ DnsAnswer *validated_keys;
+
usec_t start_usec;
usec_t next_attempt_after;
sd_event_source *timeout_event_source;
unsigned n_attempts;
+ /* UDP connection logic, if we need it */
int dns_udp_fd;
sd_event_source *dns_udp_event_source;
+ /* TCP connection logic, if we need it */
+ DnsStream *stream;
+
/* The active server */
DnsServer *server;
- /* the features of the DNS server at time of transaction start */
- DnsServerFeatureLevel current_features;
-
- /* TCP connection logic, if we need it */
- DnsStream *stream;
+ /* The features of the DNS server at time of transaction start */
+ DnsServerFeatureLevel current_feature_level;
/* Query candidates this transaction is referenced by and that
* shall be notified about this specific transaction
* completing. */
- Set *query_candidates;
+ Set *notify_query_candidates;
/* Zone items this transaction is referenced by and that shall
* be notified about completion. */
- Set *zone_items;
+ Set *notify_zone_items;
+
+ /* Other transactions that this transactions is referenced by
+ * and that shall be notified about completion. This is used
+ * when transactions want to validate their RRsets, but need
+ * another DNSKEY or DS RR to do so. */
+ Set *notify_transactions;
+
+ /* The opposite direction: the transactions this transaction
+ * created in order to request DNSKEY or DS RRs. */
+ Set *dnssec_transactions;
unsigned block_gc;
@@ -106,12 +142,18 @@ struct DnsTransaction {
int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key);
DnsTransaction* dns_transaction_free(DnsTransaction *t);
-void dns_transaction_gc(DnsTransaction *t);
+bool dns_transaction_gc(DnsTransaction *t);
int dns_transaction_go(DnsTransaction *t);
void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p);
void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state);
+void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source);
+int dns_transaction_validate_dnssec(DnsTransaction *t);
+int dns_transaction_request_dnssec_keys(DnsTransaction *t);
+
+const char *dns_transaction_key_string(DnsTransaction *t);
+
const char* dns_transaction_state_to_string(DnsTransactionState p) _const_;
DnsTransactionState dns_transaction_state_from_string(const char *s) _pure_;
diff --git a/src/resolve/resolved-dns-trust-anchor.c b/src/resolve/resolved-dns-trust-anchor.c
index e55bdaa1ed..9bee44b5c7 100644
--- a/src/resolve/resolved-dns-trust-anchor.c
+++ b/src/resolve/resolved-dns-trust-anchor.c
@@ -19,26 +19,56 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
+#include <sd-messages.h>
+
#include "alloc-util.h"
+#include "conf-files.h"
+#include "def.h"
+#include "dns-domain.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "hexdecoct.h"
+#include "parse-util.h"
#include "resolved-dns-trust-anchor.h"
+#include "resolved-dns-dnssec.h"
+#include "set.h"
+#include "string-util.h"
+#include "strv.h"
+
+static const char trust_anchor_dirs[] = CONF_PATHS_NULSTR("dnssec-trust-anchors.d");
-/* The DS RR from https://data.iana.org/root-anchors/root-anchors.xml */
+/* The DS RR from https://data.iana.org/root-anchors/root-anchors.xml, retrieved December 2015 */
static const uint8_t root_digest[] =
{ 0x49, 0xAA, 0xC1, 0x1D, 0x7B, 0x6F, 0x64, 0x46, 0x70, 0x2E, 0x54, 0xA1, 0x60, 0x73, 0x71, 0x60,
0x7A, 0x1A, 0x41, 0x85, 0x52, 0x00, 0xFD, 0x2C, 0xE1, 0xCD, 0xDE, 0x32, 0xF2, 0x4E, 0x8F, 0xB5 };
-int dns_trust_anchor_load(DnsTrustAnchor *d) {
+static bool dns_trust_anchor_knows_domain_positive(DnsTrustAnchor *d, const char *name) {
+ assert(d);
+
+ /* Returns true if there's an entry for the specified domain
+ * name in our trust anchor */
+
+ return
+ hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DNSKEY, name)) ||
+ hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DS, name));
+}
+
+static int dns_trust_anchor_add_builtin_positive(DnsTrustAnchor *d) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
int r;
assert(d);
- r = hashmap_ensure_allocated(&d->by_key, &dns_resource_key_hash_ops);
+ r = hashmap_ensure_allocated(&d->positive_by_key, &dns_resource_key_hash_ops);
if (r < 0)
return r;
- if (hashmap_get(d->by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DS, ".")))
+ /* Only add the built-in trust anchor if there's neither a DS
+ * nor a DNSKEY defined for the root domain. That way users
+ * have an easy way to override the root domain DS/DNSKEY
+ * data. */
+ if (dns_trust_anchor_knows_domain_positive(d, "."))
return 0;
/* Add the RR from https://data.iana.org/root-anchors/root-anchors.xml */
@@ -58,30 +88,445 @@ int dns_trust_anchor_load(DnsTrustAnchor *d) {
if (!answer)
return -ENOMEM;
- r = dns_answer_add(answer, rr, 0);
+ r = dns_answer_add(answer, rr, 0, DNS_ANSWER_AUTHENTICATED);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(d->positive_by_key, rr->key, answer);
if (r < 0)
return r;
- r = hashmap_put(d->by_key, rr->key, answer);
+ answer = NULL;
+ return 0;
+}
+
+static int dns_trust_anchor_add_builtin_negative(DnsTrustAnchor *d) {
+
+ static const char private_domains[] =
+ /* RFC 6761 says that .test is a special domain for
+ * testing and not to be installed in the root zone */
+ "test\0"
+
+ /* RFC 6761 says that these reverse IP lookup ranges
+ * are for private addresses, and hence should not
+ * show up in the root zone */
+ "10.in-addr.arpa\0"
+ "16.172.in-addr.arpa\0"
+ "17.172.in-addr.arpa\0"
+ "18.172.in-addr.arpa\0"
+ "19.172.in-addr.arpa\0"
+ "20.172.in-addr.arpa\0"
+ "21.172.in-addr.arpa\0"
+ "22.172.in-addr.arpa\0"
+ "23.172.in-addr.arpa\0"
+ "24.172.in-addr.arpa\0"
+ "25.172.in-addr.arpa\0"
+ "26.172.in-addr.arpa\0"
+ "27.172.in-addr.arpa\0"
+ "28.172.in-addr.arpa\0"
+ "29.172.in-addr.arpa\0"
+ "30.172.in-addr.arpa\0"
+ "31.172.in-addr.arpa\0"
+ "168.192.in-addr.arpa\0"
+
+ /* RFC 6762 reserves the .local domain for Multicast
+ * DNS, it hence cannot appear in the root zone. (Note
+ * that we by default do not route .local traffic to
+ * DNS anyway, except when a configured search domain
+ * suggests so.) */
+ "local\0"
+
+ /* These two are well known, popular private zone
+ * TLDs, that are blocked from delegation, according
+ * to:
+ * http://icannwiki.com/Name_Collision#NGPC_Resolution
+ *
+ * There's also ongoing work on making this official
+ * in an RRC:
+ * https://www.ietf.org/archive/id/draft-chapin-additional-reserved-tlds-02.txt */
+ "home\0"
+ "corp\0"
+
+ /* The following four TLDs are suggested for private
+ * zones in RFC 6762, Appendix G, and are hence very
+ * unlikely to be made official TLDs any day soon */
+ "lan\0"
+ "intranet\0"
+ "internal\0"
+ "private\0";
+
+ const char *name;
+ int r;
+
+ assert(d);
+
+ /* Only add the built-in trust anchor if there's no negative
+ * trust anchor defined at all. This enables easy overriding
+ * of negative trust anchors. */
+
+ if (set_size(d->negative_by_name) > 0)
+ return 0;
+
+ r = set_ensure_allocated(&d->negative_by_name, &dns_name_hash_ops);
if (r < 0)
return r;
+ /* We add a couple of domains as default negative trust
+ * anchors, where it's very unlikely they will be installed in
+ * the root zone. If they exist they must be private, and thus
+ * unsigned. */
+
+ NULSTR_FOREACH(name, private_domains) {
+
+ if (dns_trust_anchor_knows_domain_positive(d, name))
+ continue;
+
+ r = set_put_strdup(d->negative_by_name, name);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, unsigned line, const char *s) {
+ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
+ _cleanup_free_ char *domain = NULL, *class = NULL, *type = NULL;
+ _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
+ DnsAnswer *old_answer = NULL;
+ const char *p = s;
+ int r;
+
+ assert(d);
+ assert(line);
+
+ r = extract_first_word(&p, &domain, NULL, EXTRACT_QUOTES);
+ if (r < 0)
+ return log_warning_errno(r, "Unable to parse domain in line %s:%u: %m", path, line);
+
+ if (!dns_name_is_valid(domain)) {
+ log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain, path, line);
+ return -EINVAL;
+ }
+
+ r = extract_many_words(&p, NULL, 0, &class, &type, NULL);
+ if (r < 0)
+ return log_warning_errno(r, "Unable to parse class and type in line %s:%u: %m", path, line);
+ if (r != 2) {
+ log_warning("Missing class or type in line %s:%u", path, line);
+ return -EINVAL;
+ }
+
+ if (!strcaseeq(class, "IN")) {
+ log_warning("RR class %s is not supported, ignoring line %s:%u.", class, path, line);
+ return -EINVAL;
+ }
+
+ if (strcaseeq(type, "DS")) {
+ _cleanup_free_ char *key_tag = NULL, *algorithm = NULL, *digest_type = NULL, *digest = NULL;
+ _cleanup_free_ void *dd = NULL;
+ uint16_t kt;
+ int a, dt;
+ size_t l;
+
+ r = extract_many_words(&p, NULL, 0, &key_tag, &algorithm, &digest_type, &digest, NULL);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to parse DS parameters on line %s:%u: %m", path, line);
+ return -EINVAL;
+ }
+ if (r != 4) {
+ log_warning("Missing DS parameters on line %s:%u", path, line);
+ return -EINVAL;
+ }
+
+ r = safe_atou16(key_tag, &kt);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to parse DS key tag %s on line %s:%u: %m", key_tag, path, line);
+
+ a = dnssec_algorithm_from_string(algorithm);
+ if (a < 0) {
+ log_warning("Failed to parse DS algorithm %s on line %s:%u", algorithm, path, line);
+ return -EINVAL;
+ }
+
+ dt = dnssec_digest_from_string(digest_type);
+ if (dt < 0) {
+ log_warning("Failed to parse DS digest type %s on line %s:%u", digest_type, path, line);
+ return -EINVAL;
+ }
+
+ r = unhexmem(digest, strlen(digest), &dd, &l);
+ if (r < 0) {
+ log_warning("Failed to parse DS digest %s on line %s:%u", digest, path, line);
+ return -EINVAL;
+ }
+
+ rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, domain);
+ if (!rr)
+ return log_oom();
+
+ rr->ds.key_tag = kt;
+ rr->ds.algorithm = a;
+ rr->ds.digest_type = dt;
+ rr->ds.digest_size = l;
+ rr->ds.digest = dd;
+ dd = NULL;
+
+ } else if (strcaseeq(type, "DNSKEY")) {
+ _cleanup_free_ char *flags = NULL, *protocol = NULL, *algorithm = NULL, *key = NULL;
+ _cleanup_free_ void *k = NULL;
+ uint16_t f;
+ size_t l;
+ int a;
+
+ r = extract_many_words(&p, NULL, 0, &flags, &protocol, &algorithm, &key, NULL);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to parse DNSKEY parameters on line %s:%u: %m", path, line);
+ if (r != 4) {
+ log_warning("Missing DNSKEY parameters on line %s:%u", path, line);
+ return -EINVAL;
+ }
+
+ if (!streq(protocol, "3")) {
+ log_warning("DNSKEY Protocol is not 3 on line %s:%u", path, line);
+ return -EINVAL;
+ }
+
+ r = safe_atou16(flags, &f);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to parse DNSKEY flags field %s on line %s:%u", flags, path, line);
+ if ((f & DNSKEY_FLAG_ZONE_KEY) == 0) {
+ log_warning("DNSKEY lacks zone key bit set on line %s:%u", path, line);
+ return -EINVAL;
+ }
+ if ((f & DNSKEY_FLAG_REVOKE)) {
+ log_warning("DNSKEY is already revoked on line %s:%u", path, line);
+ return -EINVAL;
+ }
+
+ a = dnssec_algorithm_from_string(algorithm);
+ if (a < 0) {
+ log_warning("Failed to parse DNSKEY algorithm %s on line %s:%u", algorithm, path, line);
+ return -EINVAL;
+ }
+
+ r = unbase64mem(key, strlen(key), &k, &l);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to parse DNSKEY key data %s on line %s:%u", key, path, line);
+
+ rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, domain);
+ if (!rr)
+ return log_oom();
+
+ rr->dnskey.flags = f;
+ rr->dnskey.protocol = 3;
+ rr->dnskey.algorithm = a;
+ rr->dnskey.key_size = l;
+ rr->dnskey.key = k;
+ k = NULL;
+
+ } else {
+ log_warning("RR type %s is not supported, ignoring line %s:%u.", type, path, line);
+ return -EINVAL;
+ }
+
+ if (!isempty(p)) {
+ log_warning("Trailing garbage on line %s:%u, ignoring line.", path, line);
+ return -EINVAL;
+ }
+
+ r = hashmap_ensure_allocated(&d->positive_by_key, &dns_resource_key_hash_ops);
+ if (r < 0)
+ return log_oom();
+
+ old_answer = hashmap_get(d->positive_by_key, rr->key);
+ answer = dns_answer_ref(old_answer);
+
+ r = dns_answer_add_extend(&answer, rr, 0, DNS_ANSWER_AUTHENTICATED);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add trust anchor RR: %m");
+
+ r = hashmap_replace(d->positive_by_key, rr->key, answer);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add answer to trust anchor: %m");
+
+ old_answer = dns_answer_unref(old_answer);
answer = NULL;
+
+ return 0;
+}
+
+static int dns_trust_anchor_load_negative(DnsTrustAnchor *d, const char *path, unsigned line, const char *s) {
+ _cleanup_free_ char *domain = NULL;
+ const char *p = s;
+ int r;
+
+ assert(d);
+ assert(line);
+
+ r = extract_first_word(&p, &domain, NULL, EXTRACT_QUOTES);
+ if (r < 0)
+ return log_warning_errno(r, "Unable to parse line %s:%u: %m", path, line);
+
+ if (!dns_name_is_valid(domain)) {
+ log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain, path, line);
+ return -EINVAL;
+ }
+
+ if (!isempty(p)) {
+ log_warning("Trailing garbage at line %s:%u, ignoring line.", path, line);
+ return -EINVAL;
+ }
+
+ r = set_ensure_allocated(&d->negative_by_name, &dns_name_hash_ops);
+ if (r < 0)
+ return log_oom();
+
+ r = set_put(d->negative_by_name, domain);
+ if (r < 0)
+ return log_oom();
+ if (r > 0)
+ domain = NULL;
+
+ return 0;
+}
+
+static int dns_trust_anchor_load_files(
+ DnsTrustAnchor *d,
+ const char *suffix,
+ int (*loader)(DnsTrustAnchor *d, const char *path, unsigned n, const char *line)) {
+
+ _cleanup_strv_free_ char **files = NULL;
+ char **f;
+ int r;
+
+ assert(d);
+ assert(suffix);
+ assert(loader);
+
+ r = conf_files_list_nulstr(&files, suffix, NULL, trust_anchor_dirs);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enumerate %s trust anchor files: %m", suffix);
+
+ STRV_FOREACH(f, files) {
+ _cleanup_fclose_ FILE *g = NULL;
+ char line[LINE_MAX];
+ unsigned n = 0;
+
+ g = fopen(*f, "r");
+ if (!g) {
+ if (errno == ENOENT)
+ continue;
+
+ log_warning_errno(errno, "Failed to open %s: %m", *f);
+ continue;
+ }
+
+ FOREACH_LINE(line, g, log_warning_errno(errno, "Failed to read %s, ignoring: %m", *f)) {
+ char *l;
+
+ n++;
+
+ l = strstrip(line);
+ if (isempty(l))
+ continue;
+
+ if (*l == ';')
+ continue;
+
+ (void) loader(d, *f, n, l);
+ }
+ }
+
+ return 0;
+}
+
+static int domain_name_cmp(const void *a, const void *b) {
+ char **x = (char**) a, **y = (char**) b;
+
+ return dns_name_compare_func(*x, *y);
+}
+
+static int dns_trust_anchor_dump(DnsTrustAnchor *d) {
+ DnsAnswer *a;
+ Iterator i;
+
+ assert(d);
+
+ if (hashmap_isempty(d->positive_by_key))
+ log_info("No positive trust anchors defined.");
+ else {
+ log_info("Positive Trust Anchors:");
+ HASHMAP_FOREACH(a, d->positive_by_key, i) {
+ DnsResourceRecord *rr;
+
+ DNS_ANSWER_FOREACH(rr, a)
+ log_info("%s", dns_resource_record_to_string(rr));
+ }
+ }
+
+ if (set_isempty(d->negative_by_name))
+ log_info("No negative trust anchors defined.");
+ else {
+ _cleanup_free_ char **l = NULL, *j = NULL;
+
+ l = set_get_strv(d->negative_by_name);
+ if (!l)
+ return log_oom();
+
+ qsort_safe(l, set_size(d->negative_by_name), sizeof(char*), domain_name_cmp);
+
+ j = strv_join(l, " ");
+ if (!j)
+ return log_oom();
+
+ log_info("Negative trust anchors: %s", j);
+ }
+
+ return 0;
+}
+
+int dns_trust_anchor_load(DnsTrustAnchor *d) {
+ int r;
+
+ assert(d);
+
+ /* If loading things from disk fails, we don't consider this fatal */
+ (void) dns_trust_anchor_load_files(d, ".positive", dns_trust_anchor_load_positive);
+ (void) dns_trust_anchor_load_files(d, ".negative", dns_trust_anchor_load_negative);
+
+ /* However, if the built-in DS fails, then we have a problem. */
+ r = dns_trust_anchor_add_builtin_positive(d);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add built-in positive trust anchor: %m");
+
+ r = dns_trust_anchor_add_builtin_negative(d);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add built-in negative trust anchor: %m");
+
+ dns_trust_anchor_dump(d);
+
return 0;
}
void dns_trust_anchor_flush(DnsTrustAnchor *d) {
DnsAnswer *a;
+ DnsResourceRecord *rr;
assert(d);
- while ((a = hashmap_steal_first(d->by_key)))
+ while ((a = hashmap_steal_first(d->positive_by_key)))
dns_answer_unref(a);
+ d->positive_by_key = hashmap_free(d->positive_by_key);
+
+ while ((rr = set_steal_first(d->revoked_by_rr)))
+ dns_resource_record_unref(rr);
+ d->revoked_by_rr = set_free(d->revoked_by_rr);
- d->by_key = hashmap_free(d->by_key);
+ d->negative_by_name = set_free_free(d->negative_by_name);
}
-int dns_trust_anchor_lookup(DnsTrustAnchor *d, DnsResourceKey *key, DnsAnswer **ret) {
+int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey *key, DnsAnswer **ret) {
DnsAnswer *a;
assert(d);
@@ -92,10 +537,209 @@ int dns_trust_anchor_lookup(DnsTrustAnchor *d, DnsResourceKey *key, DnsAnswer **
if (!IN_SET(key->type, DNS_TYPE_DS, DNS_TYPE_DNSKEY))
return 0;
- a = hashmap_get(d->by_key, key);
+ a = hashmap_get(d->positive_by_key, key);
if (!a)
return 0;
*ret = dns_answer_ref(a);
return 1;
}
+
+int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name) {
+ assert(d);
+ assert(name);
+
+ return set_contains(d->negative_by_name, name);
+}
+
+static int dns_trust_anchor_revoked_put(DnsTrustAnchor *d, DnsResourceRecord *rr) {
+ int r;
+
+ assert(d);
+
+ r = set_ensure_allocated(&d->revoked_by_rr, &dns_resource_record_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = set_put(d->revoked_by_rr, rr);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ dns_resource_record_ref(rr);
+
+ return r;
+}
+
+static int dns_trust_anchor_remove_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr) {
+ _cleanup_(dns_answer_unrefp) DnsAnswer *new_answer = NULL;
+ DnsAnswer *old_answer;
+ int r;
+
+ /* Remember that this is a revoked trust anchor RR */
+ r = dns_trust_anchor_revoked_put(d, rr);
+ if (r < 0)
+ return r;
+
+ /* Remove this from the positive trust anchor */
+ old_answer = hashmap_get(d->positive_by_key, rr->key);
+ if (!old_answer)
+ return 0;
+
+ new_answer = dns_answer_ref(old_answer);
+
+ r = dns_answer_remove_by_rr(&new_answer, rr);
+ if (r <= 0)
+ return r;
+
+ /* We found the key! Warn the user */
+ log_struct(LOG_WARNING,
+ LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED),
+ LOG_MESSAGE("DNSSEC Trust anchor %s has been revoked. Please update the trust anchor, or upgrade your operating system."), strna(dns_resource_record_to_string(rr)),
+ "TRUST_ANCHOR=%s", dns_resource_record_to_string(rr),
+ NULL);
+
+ if (dns_answer_size(new_answer) <= 0) {
+ assert_se(hashmap_remove(d->positive_by_key, rr->key) == old_answer);
+ dns_answer_unref(old_answer);
+ return 1;
+ }
+
+ r = hashmap_replace(d->positive_by_key, new_answer->items[0].rr->key, new_answer);
+ if (r < 0)
+ return r;
+
+ new_answer = NULL;
+ dns_answer_unref(old_answer);
+ return 1;
+}
+
+static int dns_trust_anchor_check_revoked_one(DnsTrustAnchor *d, DnsResourceRecord *revoked_dnskey) {
+ DnsAnswer *a;
+ int r;
+
+ assert(d);
+ assert(revoked_dnskey);
+ assert(revoked_dnskey->key->type == DNS_TYPE_DNSKEY);
+ assert(revoked_dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE);
+
+ a = hashmap_get(d->positive_by_key, revoked_dnskey->key);
+ if (a) {
+ DnsResourceRecord *anchor;
+
+ /* First, look for the precise DNSKEY in our trust anchor database */
+
+ DNS_ANSWER_FOREACH(anchor, a) {
+
+ if (anchor->dnskey.protocol != revoked_dnskey->dnskey.protocol)
+ continue;
+
+ if (anchor->dnskey.algorithm != revoked_dnskey->dnskey.algorithm)
+ continue;
+
+ if (anchor->dnskey.key_size != revoked_dnskey->dnskey.key_size)
+ continue;
+
+ /* Note that we allow the REVOKE bit to be
+ * different! It will be set in the revoked
+ * key, but unset in our version of it */
+ if (((anchor->dnskey.flags ^ revoked_dnskey->dnskey.flags) | DNSKEY_FLAG_REVOKE) != DNSKEY_FLAG_REVOKE)
+ continue;
+
+ if (memcmp(anchor->dnskey.key, revoked_dnskey->dnskey.key, anchor->dnskey.key_size) != 0)
+ continue;
+
+ dns_trust_anchor_remove_revoked(d, anchor);
+ break;
+ }
+ }
+
+ a = hashmap_get(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(revoked_dnskey->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(revoked_dnskey->key)));
+ if (a) {
+ DnsResourceRecord *anchor;
+
+ /* Second, look for DS RRs matching this DNSKEY in our trust anchor database */
+
+ DNS_ANSWER_FOREACH(anchor, a) {
+
+ /* We set mask_revoke to true here, since our
+ * DS fingerprint will be the one of the
+ * unrevoked DNSKEY, but the one we got passed
+ * here has the bit set. */
+ r = dnssec_verify_dnskey(revoked_dnskey, anchor, true);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ dns_trust_anchor_remove_revoked(d, anchor);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsResourceRecord *dnskey, DnsAnswer *rrs) {
+ DnsResourceRecord *rrsig;
+ int r;
+
+ assert(d);
+ assert(dnskey);
+
+ /* Looks if "dnskey" is a self-signed RR that has been revoked
+ * and matches one of our trust anchor entries. If so, removes
+ * it from the trust anchor and returns > 0. */
+
+ if (dnskey->key->type != DNS_TYPE_DNSKEY)
+ return 0;
+
+ /* Is this DNSKEY revoked? */
+ if ((dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE) == 0)
+ return 0;
+
+ /* Could this be interesting to us at all? If not,
+ * there's no point in looking for and verifying a
+ * self-signed RRSIG. */
+ if (!dns_trust_anchor_knows_domain_positive(d, DNS_RESOURCE_KEY_NAME(dnskey->key)))
+ return 0;
+
+ /* Look for a self-signed RRSIG in the other rrs belonging to this DNSKEY */
+ DNS_ANSWER_FOREACH(rrsig, rrs) {
+ DnssecResult result;
+
+ if (rrsig->key->type != DNS_TYPE_RRSIG)
+ continue;
+
+ r = dnssec_rrsig_match_dnskey(rrsig, dnskey, true);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ r = dnssec_verify_rrset(rrs, dnskey->key, rrsig, dnskey, USEC_INFINITY, &result);
+ if (r < 0)
+ return r;
+ if (result != DNSSEC_VALIDATED)
+ continue;
+
+ /* Bingo! This is a revoked self-signed DNSKEY. Let's
+ * see if this precise one exists in our trust anchor
+ * database, too. */
+ r = dns_trust_anchor_check_revoked_one(d, dnskey);
+ if (r < 0)
+ return r;
+
+ return 1;
+ }
+
+ return 0;
+}
+
+int dns_trust_anchor_is_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr) {
+ assert(d);
+
+ if (!IN_SET(rr->key->type, DNS_TYPE_DS, DNS_TYPE_DNSKEY))
+ return 0;
+
+ return set_contains(d->revoked_by_rr, rr);
+}
diff --git a/src/resolve/resolved-dns-trust-anchor.h b/src/resolve/resolved-dns-trust-anchor.h
index 06f3723914..5d137faae1 100644
--- a/src/resolve/resolved-dns-trust-anchor.h
+++ b/src/resolve/resolved-dns-trust-anchor.h
@@ -30,10 +30,16 @@ typedef struct DnsTrustAnchor DnsTrustAnchor;
/* This contains a fixed database mapping domain names to DS or DNSKEY records. */
struct DnsTrustAnchor {
- Hashmap *by_key;
+ Hashmap *positive_by_key;
+ Set *negative_by_name;
+ Set *revoked_by_rr;
};
int dns_trust_anchor_load(DnsTrustAnchor *d);
void dns_trust_anchor_flush(DnsTrustAnchor *d);
-int dns_trust_anchor_lookup(DnsTrustAnchor *d, DnsResourceKey* key, DnsAnswer **answer);
+int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey* key, DnsAnswer **answer);
+int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name);
+
+int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsResourceRecord *dnskey, DnsAnswer *rrs);
+int dns_trust_anchor_is_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr);
diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c
index 78f44d51a2..f60b0bddc1 100644
--- a/src/resolve/resolved-dns-zone.c
+++ b/src/resolve/resolved-dns-zone.c
@@ -39,7 +39,7 @@ void dns_zone_item_probe_stop(DnsZoneItem *i) {
t = i->probe_transaction;
i->probe_transaction = NULL;
- set_remove(t->zone_items, i);
+ set_remove(t->notify_zone_items, i);
dns_transaction_gc(t);
}
@@ -184,11 +184,11 @@ static int dns_zone_item_probe_start(DnsZoneItem *i) {
return r;
}
- r = set_ensure_allocated(&t->zone_items, NULL);
+ r = set_ensure_allocated(&t->notify_zone_items, NULL);
if (r < 0)
goto gc;
- r = set_put(t->zone_items, i);
+ r = set_put(t->notify_zone_items, i);
if (r < 0)
goto gc;
@@ -206,7 +206,7 @@ static int dns_zone_item_probe_start(DnsZoneItem *i) {
}
}
- dns_zone_item_ready(i);
+ dns_zone_item_notify(i);
return 0;
gc:
@@ -223,9 +223,9 @@ int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) {
assert(s);
assert(rr);
- if (rr->key->class == DNS_CLASS_ANY)
+ if (dns_class_is_pseudo(rr->key->class))
return -EINVAL;
- if (rr->key->type == DNS_TYPE_ANY)
+ if (dns_type_is_pseudo(rr->key->type))
return -EINVAL;
existing = dns_zone_get(z, rr);
@@ -386,7 +386,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, Dns
if (k < 0)
return k;
if (k > 0) {
- r = dns_answer_add(answer, j->rr, 0);
+ r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED);
if (r < 0)
return r;
@@ -412,7 +412,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, Dns
if (j->state != DNS_ZONE_ITEM_PROBING)
tentative = false;
- r = dns_answer_add(answer, j->rr, 0);
+ r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED);
if (r < 0)
return r;
}
@@ -471,15 +471,12 @@ return_empty:
}
void dns_zone_item_conflict(DnsZoneItem *i) {
- _cleanup_free_ char *pretty = NULL;
-
assert(i);
if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED))
return;
- dns_resource_record_to_string(i->rr, &pretty);
- log_info("Detected conflict on %s", strna(pretty));
+ log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr)));
dns_zone_item_probe_stop(i);
@@ -491,16 +488,14 @@ void dns_zone_item_conflict(DnsZoneItem *i) {
manager_next_hostname(i->scope->manager);
}
-void dns_zone_item_ready(DnsZoneItem *i) {
- _cleanup_free_ char *pretty = NULL;
-
+void dns_zone_item_notify(DnsZoneItem *i) {
assert(i);
assert(i->probe_transaction);
if (i->block_ready > 0)
return;
- if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING))
+ if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING))
return;
if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) {
@@ -530,15 +525,13 @@ void dns_zone_item_ready(DnsZoneItem *i) {
log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
}
- dns_resource_record_to_string(i->rr, &pretty);
- log_debug("Record %s successfully probed.", strna(pretty));
+ log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i->rr)));
dns_zone_item_probe_stop(i);
i->state = DNS_ZONE_ITEM_ESTABLISHED;
}
static int dns_zone_item_verify(DnsZoneItem *i) {
- _cleanup_free_ char *pretty = NULL;
int r;
assert(i);
@@ -546,8 +539,7 @@ static int dns_zone_item_verify(DnsZoneItem *i) {
if (i->state != DNS_ZONE_ITEM_ESTABLISHED)
return 0;
- dns_resource_record_to_string(i->rr, &pretty);
- log_debug("Verifying RR %s", strna(pretty));
+ log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i->rr)));
i->state = DNS_ZONE_ITEM_VERIFYING;
r = dns_zone_item_probe_start(i);
@@ -632,7 +624,6 @@ void dns_zone_verify_all(DnsZone *zone) {
void dns_zone_dump(DnsZone *zone, FILE *f) {
Iterator iterator;
DnsZoneItem *i;
- int r;
if (!zone)
return;
@@ -644,10 +635,10 @@ void dns_zone_dump(DnsZone *zone, FILE *f) {
DnsZoneItem *j;
LIST_FOREACH(by_key, j, i) {
- _cleanup_free_ char *t = NULL;
+ const char *t;
- r = dns_resource_record_to_string(j->rr, &t);
- if (r < 0) {
+ t = dns_resource_record_to_string(j->rr);
+ if (!t) {
log_oom();
continue;
}
diff --git a/src/resolve/resolved-dns-zone.h b/src/resolve/resolved-dns-zone.h
index 44a8624c30..dbd6a2a368 100644
--- a/src/resolve/resolved-dns-zone.h
+++ b/src/resolve/resolved-dns-zone.h
@@ -70,7 +70,7 @@ void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr);
int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **answer, DnsAnswer **soa, bool *tentative);
void dns_zone_item_conflict(DnsZoneItem *i);
-void dns_zone_item_ready(DnsZoneItem *i);
+void dns_zone_item_notify(DnsZoneItem *i);
int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr);
int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key);
diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf
index c815eae850..c5ad04afd7 100644
--- a/src/resolve/resolved-gperf.gperf
+++ b/src/resolve/resolved-gperf.gperf
@@ -14,8 +14,9 @@ struct ConfigPerfItem;
%struct-type
%includes
%%
-Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0
-Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0
-Resolve.Domains, config_parse_search_domains, 0, 0
-Resolve.LLMNR, config_parse_support, 0, offsetof(Manager, llmnr_support)
-Resolve.DNSSEC, config_parse_dnssec, 0, 0
+Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0
+Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0
+Resolve.Domains, config_parse_search_domains, 0, 0
+Resolve.LLMNR, config_parse_resolve_support, 0, offsetof(Manager, llmnr_support)
+Resolve.MulticastDNS, config_parse_resolve_support, 0, offsetof(Manager, mdns_support)
+Resolve.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Manager, dnssec_mode)
diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c
index 84100bd988..928307e004 100644
--- a/src/resolve/resolved-link.c
+++ b/src/resolve/resolved-link.c
@@ -46,7 +46,9 @@ int link_new(Manager *m, Link **ret, int ifindex) {
return -ENOMEM;
l->ifindex = ifindex;
- l->llmnr_support = SUPPORT_YES;
+ l->llmnr_support = RESOLVE_SUPPORT_YES;
+ l->mdns_support = RESOLVE_SUPPORT_NO;
+ l->dnssec_mode = _DNSSEC_MODE_INVALID;
r = hashmap_put(m->links, INT_TO_PTR(ifindex), l);
if (r < 0)
@@ -65,7 +67,7 @@ Link *link_free(Link *l) {
if (!l)
return NULL;
- dns_server_unlink_marked(l->dns_servers);
+ dns_server_unlink_all(l->dns_servers);
dns_search_domain_unlink_all(l->search_domains);
while (l->addresses)
@@ -80,6 +82,8 @@ Link *link_free(Link *l) {
dns_scope_free(l->mdns_ipv4_scope);
dns_scope_free(l->mdns_ipv6_scope);
+ set_free_free(l->dnssec_negative_trust_anchors);
+
free(l);
return NULL;
}
@@ -99,8 +103,8 @@ static void link_allocate_scopes(Link *l) {
l->unicast_scope = dns_scope_free(l->unicast_scope);
if (link_relevant(l, AF_INET) &&
- l->llmnr_support != SUPPORT_NO &&
- l->manager->llmnr_support != SUPPORT_NO) {
+ l->llmnr_support != RESOLVE_SUPPORT_NO &&
+ l->manager->llmnr_support != RESOLVE_SUPPORT_NO) {
if (!l->llmnr_ipv4_scope) {
r = dns_scope_new(l->manager, &l->llmnr_ipv4_scope, l, DNS_PROTOCOL_LLMNR, AF_INET);
if (r < 0)
@@ -110,8 +114,8 @@ static void link_allocate_scopes(Link *l) {
l->llmnr_ipv4_scope = dns_scope_free(l->llmnr_ipv4_scope);
if (link_relevant(l, AF_INET6) &&
- l->llmnr_support != SUPPORT_NO &&
- l->manager->llmnr_support != SUPPORT_NO &&
+ l->llmnr_support != RESOLVE_SUPPORT_NO &&
+ l->manager->llmnr_support != RESOLVE_SUPPORT_NO &&
socket_ipv6_is_supported()) {
if (!l->llmnr_ipv6_scope) {
r = dns_scope_new(l->manager, &l->llmnr_ipv6_scope, l, DNS_PROTOCOL_LLMNR, AF_INET6);
@@ -122,8 +126,8 @@ static void link_allocate_scopes(Link *l) {
l->llmnr_ipv6_scope = dns_scope_free(l->llmnr_ipv6_scope);
if (link_relevant(l, AF_INET) &&
- l->mdns_support != SUPPORT_NO &&
- l->manager->mdns_support != SUPPORT_NO) {
+ l->mdns_support != RESOLVE_SUPPORT_NO &&
+ l->manager->mdns_support != RESOLVE_SUPPORT_NO) {
if (!l->mdns_ipv4_scope) {
r = dns_scope_new(l->manager, &l->mdns_ipv4_scope, l, DNS_PROTOCOL_MDNS, AF_INET);
if (r < 0)
@@ -133,8 +137,8 @@ static void link_allocate_scopes(Link *l) {
l->mdns_ipv4_scope = dns_scope_free(l->mdns_ipv4_scope);
if (link_relevant(l, AF_INET6) &&
- l->mdns_support != SUPPORT_NO &&
- l->manager->mdns_support != SUPPORT_NO) {
+ l->mdns_support != RESOLVE_SUPPORT_NO &&
+ l->manager->mdns_support != RESOLVE_SUPPORT_NO) {
if (!l->mdns_ipv6_scope) {
r = dns_scope_new(l->manager, &l->mdns_ipv6_scope, l, DNS_PROTOCOL_MDNS, AF_INET6);
if (r < 0)
@@ -183,6 +187,10 @@ static int link_update_dns_servers(Link *l) {
assert(l);
r = sd_network_link_get_dns(l->ifindex, &nameservers);
+ if (r == -ENODATA) {
+ r = 0;
+ goto clear;
+ }
if (r < 0)
goto clear;
@@ -222,25 +230,114 @@ static int link_update_llmnr_support(Link *l) {
assert(l);
r = sd_network_link_get_llmnr(l->ifindex, &b);
+ if (r == -ENODATA) {
+ r = 0;
+ goto clear;
+ }
if (r < 0)
goto clear;
- r = parse_boolean(b);
- if (r < 0) {
- if (streq(b, "resolve"))
- l->llmnr_support = SUPPORT_RESOLVE;
- else
- goto clear;
+ l->llmnr_support = resolve_support_from_string(b);
+ if (l->llmnr_support < 0) {
+ r = -EINVAL;
+ goto clear;
+ }
+
+ return 0;
+
+clear:
+ l->llmnr_support = RESOLVE_SUPPORT_YES;
+ return r;
+}
+
+static int link_update_mdns_support(Link *l) {
+ _cleanup_free_ char *b = NULL;
+ int r;
+
+ assert(l);
- } else if (r > 0)
- l->llmnr_support = SUPPORT_YES;
- else
- l->llmnr_support = SUPPORT_NO;
+ r = sd_network_link_get_mdns(l->ifindex, &b);
+ if (r == -ENODATA) {
+ r = 0;
+ goto clear;
+ }
+ if (r < 0)
+ goto clear;
+
+ l->mdns_support = resolve_support_from_string(b);
+ if (l->mdns_support < 0) {
+ r = -EINVAL;
+ goto clear;
+ }
return 0;
clear:
- l->llmnr_support = SUPPORT_YES;
+ l->mdns_support = RESOLVE_SUPPORT_NO;
+ return r;
+}
+
+static int link_update_dnssec_mode(Link *l) {
+ _cleanup_free_ char *m = NULL;
+ int r;
+
+ assert(l);
+
+ r = sd_network_link_get_dnssec(l->ifindex, &m);
+ if (r == -ENODATA) {
+ r = 0;
+ goto clear;
+ }
+ if (r < 0)
+ goto clear;
+
+ l->dnssec_mode = dnssec_mode_from_string(m);
+ if (l->dnssec_mode < 0) {
+ r = -EINVAL;
+ goto clear;
+ }
+
+ return 0;
+
+clear:
+ l->dnssec_mode = _DNSSEC_MODE_INVALID;
+ return r;
+}
+
+static int link_update_dnssec_negative_trust_anchors(Link *l) {
+ _cleanup_strv_free_ char **ntas = NULL;
+ _cleanup_set_free_free_ Set *ns = NULL;
+ char **i;
+ int r;
+
+ assert(l);
+
+ r = sd_network_link_get_dnssec_negative_trust_anchors(l->ifindex, &ntas);
+ if (r == -ENODATA) {
+ r = 0;
+ goto clear;
+ }
+ if (r < 0)
+ goto clear;
+
+ ns = set_new(&dns_name_hash_ops);
+ if (!ns)
+ return -ENOMEM;
+
+ STRV_FOREACH(i, ntas) {
+ r = set_put_strdup(ns, *i);
+ if (r < 0)
+ return r;
+ }
+
+ set_free_free(l->dnssec_negative_trust_anchors);
+ l->dnssec_negative_trust_anchors = ns;
+ ns = NULL;
+
+ return 0;
+
+clear:
+ l->dnssec_negative_trust_anchors = set_free_free(l->dnssec_negative_trust_anchors);
return r;
}
@@ -252,6 +349,11 @@ static int link_update_search_domains(Link *l) {
assert(l);
r = sd_network_link_get_domains(l->ifindex, &domains);
+ if (r == -ENODATA) {
+ /* networkd knows nothing about this interface, and that's fine. */
+ r = 0;
+ goto clear;
+ }
if (r < 0)
goto clear;
@@ -286,14 +388,31 @@ int link_update_monitor(Link *l) {
assert(l);
- link_update_dns_servers(l);
- link_update_llmnr_support(l);
- link_allocate_scopes(l);
+ r = link_update_dns_servers(l);
+ if (r < 0)
+ log_warning_errno(r, "Failed to read DNS servers for interface %s, ignoring: %m", l->name);
+
+ r = link_update_llmnr_support(l);
+ if (r < 0)
+ log_warning_errno(r, "Failed to read LLMNR support for interface %s, ignoring: %m", l->name);
+
+ r = link_update_mdns_support(l);
+ if (r < 0)
+ log_warning_errno(r, "Failed to read mDNS support for interface %s, ignoring: %m", l->name);
+
+ r = link_update_dnssec_mode(l);
+ if (r < 0)
+ log_warning_errno(r, "Failed to read DNSSEC mode for interface %s, ignoring: %m", l->name);
+
+ r = link_update_dnssec_negative_trust_anchors(l);
+ if (r < 0)
+ log_warning_errno(r, "Failed to read DNSSEC negative trust anchors for interface %s, ignoring: %m", l->name);
r = link_update_search_domains(l);
if (r < 0)
log_warning_errno(r, "Failed to read search domains for interface %s, ignoring: %m", l->name);
+ link_allocate_scopes(l);
link_add_rrs(l, false);
return 0;
@@ -344,12 +463,8 @@ DnsServer* link_set_dns_server(Link *l, DnsServer *s) {
if (l->current_dns_server == s)
return s;
- if (s) {
- _cleanup_free_ char *ip = NULL;
-
- in_addr_to_string(s->family, &s->address, &ip);
- log_info("Switching to DNS server %s for interface %s.", strna(ip), l->name);
- }
+ if (s)
+ log_info("Switching to DNS server %s for interface %s.", dns_server_string(s), l->name);
dns_server_unref(l->current_dns_server);
l->current_dns_server = dns_server_ref(s);
@@ -446,8 +561,8 @@ void link_address_add_rrs(LinkAddress *a, bool force_remove) {
if (!force_remove &&
link_address_relevant(a) &&
a->link->llmnr_ipv4_scope &&
- a->link->llmnr_support == SUPPORT_YES &&
- a->link->manager->llmnr_support == SUPPORT_YES) {
+ a->link->llmnr_support == RESOLVE_SUPPORT_YES &&
+ a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) {
if (!a->link->manager->llmnr_host_ipv4_key) {
a->link->manager->llmnr_host_ipv4_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, a->link->manager->llmnr_hostname);
@@ -503,8 +618,8 @@ void link_address_add_rrs(LinkAddress *a, bool force_remove) {
if (!force_remove &&
link_address_relevant(a) &&
a->link->llmnr_ipv6_scope &&
- a->link->llmnr_support == SUPPORT_YES &&
- a->link->manager->llmnr_support == SUPPORT_YES) {
+ a->link->llmnr_support == RESOLVE_SUPPORT_YES &&
+ a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) {
if (!a->link->manager->llmnr_host_ipv6_key) {
a->link->manager->llmnr_host_ipv6_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, a->link->manager->llmnr_hostname);
diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h
index a3b406bbc2..db0e51da04 100644
--- a/src/resolve/resolved-link.h
+++ b/src/resolve/resolved-link.h
@@ -25,6 +25,7 @@
#include "in-addr-util.h"
#include "ratelimit.h"
+#include "resolve-util.h"
typedef struct Link Link;
typedef struct LinkAddress LinkAddress;
@@ -66,8 +67,10 @@ struct Link {
LIST_HEAD(DnsSearchDomain, search_domains);
unsigned n_search_domains;
- Support llmnr_support;
- Support mdns_support;
+ ResolveSupport llmnr_support;
+ ResolveSupport mdns_support;
+ DnssecMode dnssec_mode;
+ Set *dnssec_negative_trust_anchors;
DnsScope *unicast_scope;
DnsScope *llmnr_ipv4_scope;
diff --git a/src/resolve/resolved-llmnr.c b/src/resolve/resolved-llmnr.c
index 6a7ff9d245..f52ab8f384 100644
--- a/src/resolve/resolved-llmnr.c
+++ b/src/resolve/resolved-llmnr.c
@@ -47,7 +47,7 @@ int manager_llmnr_start(Manager *m) {
assert(m);
- if (m->llmnr_support == SUPPORT_NO)
+ if (m->llmnr_support == RESOLVE_SUPPORT_NO)
return 0;
r = manager_llmnr_ipv4_udp_fd(m);
@@ -80,7 +80,7 @@ int manager_llmnr_start(Manager *m) {
eaddrinuse:
log_warning("There appears to be another LLMNR responder running. Turning off LLMNR support.");
- m->llmnr_support = SUPPORT_NO;
+ m->llmnr_support = RESOLVE_SUPPORT_NO;
manager_llmnr_stop(m);
return 0;
@@ -117,7 +117,7 @@ static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *u
dns_scope_process_query(scope, NULL, p);
} else
- log_debug("Invalid LLMNR UDP packet.");
+ log_debug("Invalid LLMNR UDP packet, ignoring.");
return 0;
}
@@ -193,6 +193,8 @@ int manager_llmnr_ipv4_udp_fd(Manager *m) {
if (r < 0)
goto fail;
+ (void) sd_event_source_set_description(m->llmnr_ipv4_udp_event_source, "llmnr-ipv4-udp");
+
return m->llmnr_ipv4_udp_fd;
fail:
@@ -267,10 +269,10 @@ int manager_llmnr_ipv6_udp_fd(Manager *m) {
}
r = sd_event_add_io(m->event, &m->llmnr_ipv6_udp_event_source, m->llmnr_ipv6_udp_fd, EPOLLIN, on_llmnr_packet, m);
- if (r < 0) {
- r = -errno;
+ if (r < 0)
goto fail;
- }
+
+ (void) sd_event_source_set_description(m->llmnr_ipv6_udp_event_source, "llmnr-ipv6-udp");
return m->llmnr_ipv6_udp_fd;
@@ -393,6 +395,8 @@ int manager_llmnr_ipv4_tcp_fd(Manager *m) {
if (r < 0)
goto fail;
+ (void) sd_event_source_set_description(m->llmnr_ipv4_tcp_event_source, "llmnr-ipv4-tcp");
+
return m->llmnr_ipv4_tcp_fd;
fail:
@@ -461,10 +465,10 @@ int manager_llmnr_ipv6_tcp_fd(Manager *m) {
}
r = sd_event_add_io(m->event, &m->llmnr_ipv6_tcp_event_source, m->llmnr_ipv6_tcp_fd, EPOLLIN, on_llmnr_stream, m);
- if (r < 0) {
- r = -errno;
+ if (r < 0)
goto fail;
- }
+
+ (void) sd_event_source_set_description(m->llmnr_ipv6_tcp_event_source, "llmnr-ipv6-tcp");
return m->llmnr_ipv6_tcp_fd;
diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c
index a2677f442a..b17a19d331 100644
--- a/src/resolve/resolved-manager.c
+++ b/src/resolve/resolved-manager.c
@@ -313,6 +313,8 @@ static int manager_network_monitor_listen(Manager *m) {
if (r < 0)
return r;
+ (void) sd_event_source_set_description(m->network_event_source, "network-monitor");
+
return 0;
}
@@ -420,6 +422,8 @@ static int manager_watch_hostname(Manager *m) {
return log_error_errno(r, "Failed to add hostname event source: %m");
}
+ (void) sd_event_source_set_description(m->hostname_event_source, "hostname");
+
r = determine_hostname(&m->llmnr_hostname, &m->mdns_hostname);
if (r < 0) {
log_info("Defaulting to hostname 'linux'.");
@@ -476,7 +480,9 @@ int manager_new(Manager **ret) {
m->mdns_ipv4_fd = m->mdns_ipv6_fd = -1;
m->hostname_fd = -1;
- m->llmnr_support = SUPPORT_YES;
+ m->llmnr_support = RESOLVE_SUPPORT_YES;
+ m->mdns_support = RESOLVE_SUPPORT_NO;
+ m->dnssec_mode = DNSSEC_NO;
m->read_resolv_conf = true;
m->need_builtin_fallbacks = true;
@@ -484,6 +490,10 @@ int manager_new(Manager **ret) {
if (r < 0)
return r;
+ r = manager_parse_config_file(m);
+ if (r < 0)
+ return r;
+
r = sd_event_default(&m->event);
if (r < 0)
return r;
@@ -772,7 +782,7 @@ static int write_loop(int fd, void *message, size_t length) {
int manager_write(Manager *m, int fd, DnsPacket *p) {
int r;
- log_debug("Sending %s packet with id %u", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p));
+ log_debug("Sending %s packet with id %" PRIu16 ".", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p));
r = write_loop(fd, DNS_PACKET_DATA(p), p->size);
if (r < 0)
@@ -887,7 +897,7 @@ int manager_send(Manager *m, int fd, int ifindex, int family, const union in_add
assert(port > 0);
assert(p);
- log_debug("Sending %s packet with id %u on interface %i/%s", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p), ifindex, af_to_name(family));
+ log_debug("Sending %s packet with id %" PRIu16 " on interface %i/%s.", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p), ifindex, af_to_name(family));
if (family == AF_INET)
return manager_ipv4_send(m, fd, ifindex, &addr->in, port, p);
@@ -1163,10 +1173,3 @@ int manager_compile_search_domains(Manager *m, OrderedSet **domains) {
return 0;
}
-
-static const char* const support_table[_SUPPORT_MAX] = {
- [SUPPORT_NO] = "no",
- [SUPPORT_YES] = "yes",
- [SUPPORT_RESOLVE] = "resolve",
-};
-DEFINE_STRING_TABLE_LOOKUP(support, Support);
diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h
index b52273403a..1907d2e1bc 100644
--- a/src/resolve/resolved-manager.h
+++ b/src/resolve/resolved-manager.h
@@ -28,17 +28,9 @@
#include "hashmap.h"
#include "list.h"
#include "ordered-set.h"
+#include "resolve-util.h"
typedef struct Manager Manager;
-typedef enum Support Support;
-
-enum Support {
- SUPPORT_NO,
- SUPPORT_YES,
- SUPPORT_RESOLVE,
- _SUPPORT_MAX,
- _SUPPORT_INVALID = -1
-};
#include "resolved-dns-query.h"
#include "resolved-dns-search-domain.h"
@@ -53,8 +45,9 @@ enum Support {
struct Manager {
sd_event *event;
- Support llmnr_support;
- Support mdns_support;
+ ResolveSupport llmnr_support;
+ ResolveSupport mdns_support;
+ DnssecMode dnssec_mode;
/* Network */
Hashmap *links;
@@ -128,6 +121,9 @@ struct Manager {
sd_bus_slot *prepare_for_sleep_slot;
sd_event_source *sigusr1_event_source;
+
+ unsigned n_transactions_total;
+ unsigned n_dnssec_secure, n_dnssec_insecure, n_dnssec_bogus, n_dnssec_indeterminate;
};
/* Manager */
@@ -162,6 +158,3 @@ int manager_is_own_hostname(Manager *m, const char *name);
int manager_compile_dns_servers(Manager *m, OrderedSet **servers);
int manager_compile_search_domains(Manager *m, OrderedSet **domains);
-
-const char* support_to_string(Support p) _const_;
-int support_from_string(const char *s) _pure_;
diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c
index 096a4b1fe5..d5b253d4f5 100644
--- a/src/resolve/resolved-mdns.c
+++ b/src/resolve/resolved-mdns.c
@@ -42,7 +42,7 @@ int manager_mdns_start(Manager *m) {
assert(m);
- if (m->mdns_support == SUPPORT_NO)
+ if (m->mdns_support == RESOLVE_SUPPORT_NO)
return 0;
r = manager_mdns_ipv4_fd(m);
@@ -63,7 +63,7 @@ int manager_mdns_start(Manager *m) {
eaddrinuse:
log_warning("There appears to be another mDNS responder running. Turning off mDNS support.");
- m->mdns_support = SUPPORT_NO;
+ m->mdns_support = RESOLVE_SUPPORT_NO;
manager_mdns_stop(m);
return 0;
@@ -86,7 +86,7 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us
}
if (dns_packet_validate_reply(p) > 0) {
- unsigned i;
+ DnsResourceRecord *rr;
log_debug("Got mDNS reply packet");
@@ -107,19 +107,22 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us
dns_scope_check_conflicts(scope, p);
- for (i = 0; i < p->answer->n_rrs; i++) {
- DnsResourceRecord *rr;
+ DNS_ANSWER_FOREACH(rr, p->answer) {
+ const char *name = DNS_RESOURCE_KEY_NAME(rr->key);
DnsTransaction *t;
- rr = p->answer->items[i].rr;
+ /* If the received reply packet contains ANY record that is not .local or .in-addr.arpa,
+ * we assume someone's playing tricks on us and discard the packet completely. */
+ if (!(dns_name_endswith(name, "in-addr.arpa") > 0 ||
+ dns_name_endswith(name, "local") > 0))
+ return 0;
t = dns_scope_find_transaction(scope, rr->key, false);
if (t)
dns_transaction_process_reply(t, p);
}
- dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer,
- p->answer->n_rrs, false, 0, p->family, &p->sender);
+ dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, false, (uint32_t) -1, 0, p->family, &p->sender);
} else if (dns_packet_validate_query(p) > 0) {
log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p));
@@ -275,10 +278,8 @@ int manager_mdns_ipv6_fd(Manager *m) {
}
r = sd_event_add_io(m->event, &m->mdns_ipv6_event_source, m->mdns_ipv6_fd, EPOLLIN, on_mdns_packet, m);
- if (r < 0) {
- r = -errno;
+ if (r < 0)
goto fail;
- }
return m->mdns_ipv6_fd;
diff --git a/src/resolve/resolved-resolv-conf.c b/src/resolve/resolved-resolv-conf.c
index 956f380f3c..7567f4c369 100644
--- a/src/resolve/resolved-resolv-conf.c
+++ b/src/resolve/resolved-resolv-conf.c
@@ -147,16 +147,14 @@ clear:
}
static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) {
- _cleanup_free_ char *t = NULL;
- int r;
-
assert(s);
assert(f);
assert(count);
- r = in_addr_to_string(s->family, &s->address, &t);
- if (r < 0) {
- log_warning_errno(r, "Invalid DNS address. Ignoring: %m");
+ (void) dns_server_string(s);
+
+ if (!s->server_string) {
+ log_warning("Our of memory, or invalid DNS address. Ignoring server.");
return;
}
@@ -164,7 +162,7 @@ static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) {
fputs("# Too many DNS servers configured, the following entries may be ignored.\n", f);
(*count) ++;
- fprintf(f, "nameserver %s\n", t);
+ fprintf(f, "nameserver %s\n", s->server_string);
}
static void write_resolv_conf_search(
diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c
index be406b71fe..472bb32764 100644
--- a/src/resolve/resolved.c
+++ b/src/resolve/resolved.c
@@ -81,12 +81,6 @@ int main(int argc, char *argv[]) {
goto finish;
}
- r = manager_parse_config_file(m);
- if (r < 0) {
- log_error_errno(r, "Failed to parse configuration file: %m");
- goto finish;
- }
-
r = manager_start(m);
if (r < 0) {
log_error_errno(r, "Failed to start manager: %m");
diff --git a/src/resolve/resolved.conf.in b/src/resolve/resolved.conf.in
index efc9c6733a..0ba572d113 100644
--- a/src/resolve/resolved.conf.in
+++ b/src/resolve/resolved.conf.in
@@ -16,4 +16,5 @@
#FallbackDNS=@DNS_SERVERS@
#Domains=
#LLMNR=yes
+#MulticastDNS=no
#DNSSEC=no
diff --git a/src/resolve/test-dnssec.c b/src/resolve/test-dnssec.c
index 0b2ffeeddd..0c9efde1fe 100644
--- a/src/resolve/test-dnssec.c
+++ b/src/resolve/test-dnssec.c
@@ -27,6 +27,99 @@
#include "resolved-dns-dnssec.h"
#include "resolved-dns-rr.h"
#include "string-util.h"
+#include "hexdecoct.h"
+
+static void test_dnssec_verify_rrset2(void) {
+
+ static const uint8_t signature_blob[] = {
+ 0x48, 0x45, 0xc8, 0x8b, 0xc0, 0x14, 0x92, 0xf5, 0x15, 0xc6, 0x84, 0x9d, 0x2f, 0xe3, 0x32, 0x11,
+ 0x7d, 0xf1, 0xe6, 0x87, 0xb9, 0x42, 0xd3, 0x8b, 0x9e, 0xaf, 0x92, 0x31, 0x0a, 0x53, 0xad, 0x8b,
+ 0xa7, 0x5c, 0x83, 0x39, 0x8c, 0x28, 0xac, 0xce, 0x6e, 0x9c, 0x18, 0xe3, 0x31, 0x16, 0x6e, 0xca,
+ 0x38, 0x31, 0xaf, 0xd9, 0x94, 0xf1, 0x84, 0xb1, 0xdf, 0x5a, 0xc2, 0x73, 0x22, 0xf6, 0xcb, 0xa2,
+ 0xe7, 0x8c, 0x77, 0x0c, 0x74, 0x2f, 0xc2, 0x13, 0xb0, 0x93, 0x51, 0xa9, 0x4f, 0xae, 0x0a, 0xda,
+ 0x45, 0xcc, 0xfd, 0x43, 0x99, 0x36, 0x9a, 0x0d, 0x21, 0xe0, 0xeb, 0x30, 0x65, 0xd4, 0xa0, 0x27,
+ 0x37, 0x3b, 0xe4, 0xc1, 0xc5, 0xa1, 0x2a, 0xd1, 0x76, 0xc4, 0x7e, 0x64, 0x0e, 0x5a, 0xa6, 0x50,
+ 0x24, 0xd5, 0x2c, 0xcc, 0x6d, 0xe5, 0x37, 0xea, 0xbd, 0x09, 0x34, 0xed, 0x24, 0x06, 0xa1, 0x22,
+ };
+
+ static const uint8_t dnskey_blob[] = {
+ 0x03, 0x01, 0x00, 0x01, 0xc3, 0x7f, 0x1d, 0xd1, 0x1c, 0x97, 0xb1, 0x13, 0x34, 0x3a, 0x9a, 0xea,
+ 0xee, 0xd9, 0x5a, 0x11, 0x1b, 0x17, 0xc7, 0xe3, 0xd4, 0xda, 0x20, 0xbc, 0x5d, 0xba, 0x74, 0xe3,
+ 0x37, 0x99, 0xec, 0x25, 0xce, 0x93, 0x7f, 0xbd, 0x22, 0x73, 0x7e, 0x14, 0x71, 0xe0, 0x60, 0x07,
+ 0xd4, 0x39, 0x8b, 0x5e, 0xe9, 0xba, 0x25, 0xe8, 0x49, 0xe9, 0x34, 0xef, 0xfe, 0x04, 0x5c, 0xa5,
+ 0x27, 0xcd, 0xa9, 0xda, 0x70, 0x05, 0x21, 0xab, 0x15, 0x82, 0x24, 0xc3, 0x94, 0xf5, 0xd7, 0xb7,
+ 0xc4, 0x66, 0xcb, 0x32, 0x6e, 0x60, 0x2b, 0x55, 0x59, 0x28, 0x89, 0x8a, 0x72, 0xde, 0x88, 0x56,
+ 0x27, 0x95, 0xd9, 0xac, 0x88, 0x4f, 0x65, 0x2b, 0x68, 0xfc, 0xe6, 0x41, 0xc1, 0x1b, 0xef, 0x4e,
+ 0xd6, 0xc2, 0x0f, 0x64, 0x88, 0x95, 0x5e, 0xdd, 0x3a, 0x02, 0x07, 0x50, 0xa9, 0xda, 0xa4, 0x49,
+ 0x74, 0x62, 0xfe, 0xd7,
+ };
+
+ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *nsec = NULL, *rrsig = NULL, *dnskey = NULL;
+ _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
+ DnssecResult result;
+
+ nsec = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC, "nasa.gov");
+ assert_se(nsec);
+
+ nsec->nsec.next_domain_name = strdup("3D-Printing.nasa.gov");
+ assert_se(nsec->nsec.next_domain_name);
+
+ nsec->nsec.types = bitmap_new();
+ assert_se(nsec->nsec.types);
+ assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_A) >= 0);
+ assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NS) >= 0);
+ assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_SOA) >= 0);
+ assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_MX) >= 0);
+ assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_TXT) >= 0);
+ assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_RRSIG) >= 0);
+ assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NSEC) >= 0);
+ assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_DNSKEY) >= 0);
+ assert_se(bitmap_set(nsec->nsec.types, 65534) >= 0);
+
+ log_info("NSEC: %s", strna(dns_resource_record_to_string(nsec)));
+
+ rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV.");
+ assert_se(rrsig);
+
+ rrsig->rrsig.type_covered = DNS_TYPE_NSEC;
+ rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256;
+ rrsig->rrsig.labels = 2;
+ rrsig->rrsig.original_ttl = 300;
+ rrsig->rrsig.expiration = 0x5689002f;
+ rrsig->rrsig.inception = 0x56617230;
+ rrsig->rrsig.key_tag = 30390;
+ rrsig->rrsig.signer = strdup("Nasa.Gov.");
+ assert_se(rrsig->rrsig.signer);
+ rrsig->rrsig.signature_size = sizeof(signature_blob);
+ rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size);
+ assert_se(rrsig->rrsig.signature);
+
+ log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig)));
+
+ dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV");
+ assert_se(dnskey);
+
+ dnskey->dnskey.flags = 256;
+ dnskey->dnskey.protocol = 3;
+ dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256;
+ dnskey->dnskey.key_size = sizeof(dnskey_blob);
+ dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob));
+ assert_se(dnskey->dnskey.key);
+
+ log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey)));
+ log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false));
+
+ assert_se(dnssec_key_match_rrsig(nsec->key, rrsig) > 0);
+ assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0);
+
+ answer = dns_answer_new(1);
+ assert_se(answer);
+ assert_se(dns_answer_add(answer, nsec, 0, DNS_ANSWER_AUTHENTICATED) >= 0);
+
+ /* Validate the RR as it if was 2015-12-11 today */
+ assert_se(dnssec_verify_rrset(answer, nsec->key, rrsig, dnskey, 1449849318*USEC_PER_SEC, &result) >= 0);
+ assert_se(result == DNSSEC_VALIDATED);
+}
static void test_dnssec_verify_rrset(void) {
@@ -55,15 +148,14 @@ static void test_dnssec_verify_rrset(void) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *a = NULL, *rrsig = NULL, *dnskey = NULL;
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
- _cleanup_free_ char *x = NULL, *y = NULL, *z = NULL;
+ DnssecResult result;
a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "nAsA.gov");
assert_se(a);
a->a.in_addr.s_addr = inet_addr("52.0.14.116");
- assert_se(dns_resource_record_to_string(a, &x) >= 0);
- log_info("A: %s", x);
+ log_info("A: %s", strna(dns_resource_record_to_string(a)));
rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV.");
assert_se(rrsig);
@@ -81,8 +173,7 @@ static void test_dnssec_verify_rrset(void) {
rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size);
assert_se(rrsig->rrsig.signature);
- assert_se(dns_resource_record_to_string(rrsig, &y) >= 0);
- log_info("RRSIG: %s", y);
+ log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig)));
dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV");
assert_se(dnskey);
@@ -94,19 +185,19 @@ static void test_dnssec_verify_rrset(void) {
dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob));
assert_se(dnskey->dnskey.key);
- assert_se(dns_resource_record_to_string(dnskey, &z) >= 0);
- log_info("DNSKEY: %s", z);
- log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey));
+ log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey)));
+ log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false));
assert_se(dnssec_key_match_rrsig(a->key, rrsig) > 0);
- assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey) > 0);
+ assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0);
answer = dns_answer_new(1);
assert_se(answer);
- assert_se(dns_answer_add(answer, a, 0) >= 0);
+ assert_se(dns_answer_add(answer, a, 0, DNS_ANSWER_AUTHENTICATED) >= 0);
/* Validate the RR as it if was 2015-12-2 today */
- assert_se(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, 1449092754*USEC_PER_SEC) == DNSSEC_VERIFIED);
+ assert_se(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, 1449092754*USEC_PER_SEC, &result) >= 0);
+ assert_se(result == DNSSEC_VALIDATED);
}
static void test_dnssec_verify_dns_key(void) {
@@ -140,7 +231,6 @@ static void test_dnssec_verify_dns_key(void) {
};
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *dnskey = NULL, *ds1 = NULL, *ds2 = NULL;
- _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL;
/* The two DS RRs in effect for nasa.gov on 2015-12-01. */
ds1 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "nasa.gov");
@@ -153,8 +243,7 @@ static void test_dnssec_verify_dns_key(void) {
ds1->ds.digest = memdup(ds1_fprint, ds1->ds.digest_size);
assert_se(ds1->ds.digest);
- assert_se(dns_resource_record_to_string(ds1, &a) >= 0);
- log_info("DS1: %s", a);
+ log_info("DS1: %s", strna(dns_resource_record_to_string(ds1)));
ds2 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "NASA.GOV");
assert_se(ds2);
@@ -166,8 +255,7 @@ static void test_dnssec_verify_dns_key(void) {
ds2->ds.digest = memdup(ds2_fprint, ds2->ds.digest_size);
assert_se(ds2->ds.digest);
- assert_se(dns_resource_record_to_string(ds2, &b) >= 0);
- log_info("DS2: %s", b);
+ log_info("DS2: %s", strna(dns_resource_record_to_string(ds2)));
dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nasa.GOV");
assert_se(dnskey);
@@ -179,12 +267,11 @@ static void test_dnssec_verify_dns_key(void) {
dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob));
assert_se(dnskey->dnskey.key);
- assert_se(dns_resource_record_to_string(dnskey, &c) >= 0);
- log_info("DNSKEY: %s", c);
- log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey));
+ log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey)));
+ log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false));
- assert_se(dnssec_verify_dnskey(dnskey, ds1) > 0);
- assert_se(dnssec_verify_dnskey(dnskey, ds2) > 0);
+ assert_se(dnssec_verify_dnskey(dnskey, ds1, false) > 0);
+ assert_se(dnssec_verify_dnskey(dnskey, ds2, false) > 0);
}
static void test_dnssec_canonicalize_one(const char *original, const char *canonical, int r) {
@@ -207,11 +294,45 @@ static void test_dnssec_canonicalize(void) {
test_dnssec_canonicalize_one("FOO..bar.", NULL, -EINVAL);
}
+static void test_dnssec_nsec3_hash(void) {
+ static const uint8_t salt[] = { 0xB0, 0x1D, 0xFA, 0xCE };
+ static const uint8_t next_hashed_name[] = { 0x84, 0x10, 0x26, 0x53, 0xc9, 0xfa, 0x4d, 0x85, 0x6c, 0x97, 0x82, 0xe2, 0x8f, 0xdf, 0x2d, 0x5e, 0x87, 0x69, 0xc4, 0x52 };
+ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
+ uint8_t h[DNSSEC_HASH_SIZE_MAX];
+ _cleanup_free_ char *b = NULL;
+ int k;
+
+ /* The NSEC3 RR for eurid.eu on 2015-12-14. */
+ rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC3, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM.eurid.eu.");
+ assert_se(rr);
+
+ rr->nsec3.algorithm = DNSSEC_DIGEST_SHA1;
+ rr->nsec3.flags = 1;
+ rr->nsec3.iterations = 1;
+ rr->nsec3.salt = memdup(salt, sizeof(salt));
+ assert_se(rr->nsec3.salt);
+ rr->nsec3.salt_size = sizeof(salt);
+ rr->nsec3.next_hashed_name = memdup(next_hashed_name, sizeof(next_hashed_name));
+ assert_se(rr->nsec3.next_hashed_name);
+ rr->nsec3.next_hashed_name_size = sizeof(next_hashed_name);
+
+ log_info("NSEC3: %s", strna(dns_resource_record_to_string(rr)));
+
+ k = dnssec_nsec3_hash(rr, "eurid.eu", &h);
+ assert_se(k >= 0);
+
+ b = base32hexmem(h, k, false);
+ assert_se(b);
+ assert_se(strcasecmp(b, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM") == 0);
+}
+
int main(int argc, char*argv[]) {
test_dnssec_canonicalize();
test_dnssec_verify_dns_key();
test_dnssec_verify_rrset();
+ test_dnssec_verify_rrset2();
+ test_dnssec_nsec3_hash();
return 0;
}
diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c
index e427cc1470..5c6dc34700 100644
--- a/src/shared/bus-util.c
+++ b/src/shared/bus-util.c
@@ -2381,23 +2381,28 @@ int bus_property_get_rlimit(
struct rlimit *rl;
uint64_t u;
rlim_t x;
+ const char *is_soft;
assert(bus);
assert(reply);
assert(userdata);
+ is_soft = endswith(property, "Soft");
rl = *(struct rlimit**) userdata;
if (rl)
- x = rl->rlim_max;
+ x = is_soft ? rl->rlim_cur : rl->rlim_max;
else {
struct rlimit buf = {};
int z;
+ const char *s;
+
+ s = is_soft ? strndupa(property, is_soft - property) : property;
- z = rlimit_from_string(strstr(property, "Limit"));
+ z = rlimit_from_string(strstr(s, "Limit"));
assert(z >= 0);
getrlimit(z, &buf);
- x = buf.rlim_max;
+ x = is_soft ? buf.rlim_cur : buf.rlim_max;
}
/* rlim_t might have different sizes, let's map
diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c
index 0466857042..59475115ba 100644
--- a/src/shared/dns-domain.c
+++ b/src/shared/dns-domain.c
@@ -48,7 +48,6 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
assert(name);
assert(*name);
- assert(dest);
n = *name;
d = dest;
@@ -79,9 +78,12 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
else if (*n == '\\' || *n == '.') {
/* Escaped backslash or dot */
- *(d++) = *(n++);
+
+ if (d)
+ *(d++) = *n;
sz--;
r++;
+ n++;
} else if (n[0] >= '0' && n[0] <= '9') {
unsigned k;
@@ -96,11 +98,17 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
((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)
+ /* Don't allow anything that doesn't
+ * fit in 8bit. Note that we do allow
+ * control characters, as some servers
+ * (e.g. cloudflare) are happy to
+ * generate labels with them
+ * inside. */
+ if (k > 255)
return -EINVAL;
- *(d++) = (char) k;
+ if (d)
+ *(d++) = (char) k;
sz--;
r++;
@@ -111,9 +119,12 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
} else if ((uint8_t) *n >= (uint8_t) ' ' && *n != 127) {
/* Normal character */
- *(d++) = *(n++);
+
+ if (d)
+ *(d++) = *n;
sz--;
r++;
+ n++;
} else
return -EINVAL;
}
@@ -122,7 +133,7 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
if (r == 0 && *n)
return -EINVAL;
- if (sz >= 1)
+ if (sz >= 1 && d)
*d = 0;
*name = n;
@@ -148,20 +159,24 @@ int dns_label_unescape_suffix(const char *name, const char **label_terminal, cha
return 0;
}
- assert(**label_terminal == '.' || **label_terminal == 0);
+ terminal = *label_terminal;
+ assert(*terminal == '.' || *terminal == 0);
- /* skip current terminal character */
- terminal = *label_terminal - 1;
+ /* Skip current terminal character (and accept domain names ending it ".") */
+ if (*terminal == 0)
+ terminal--;
+ if (terminal >= name && *terminal == '.')
+ terminal--;
- /* point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */
+ /* Point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */
for (;;) {
if (terminal < name) {
- /* reached the first label, so indicate that there are no more */
+ /* Reached the first label, so indicate that there are no more */
terminal = NULL;
break;
}
- /* find the start of the last label */
+ /* Find the start of the last label */
if (*terminal == '.') {
const char *y;
unsigned slashes = 0;
@@ -170,7 +185,7 @@ int dns_label_unescape_suffix(const char *name, const char **label_terminal, cha
slashes ++;
if (slashes % 2 == 0) {
- /* the '.' was not escaped */
+ /* The '.' was not escaped */
name = terminal + 1;
break;
} else {
@@ -235,7 +250,7 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
*(q++) = *p;
sz -= 1;
- } else if ((uint8_t) *p >= (uint8_t) ' ' && *p != 127) {
+ } else {
/* Everything else */
@@ -249,8 +264,7 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
sz -= 4;
- } else
- return -EINVAL;
+ }
p++;
l--;
@@ -472,13 +486,15 @@ void dns_name_hash_func(const void *s, struct siphash *state) {
assert(p);
- while (*p) {
+ for (;;) {
char label[DNS_LABEL_MAX+1];
int k;
r = dns_label_unescape(&p, label, sizeof(label));
if (r < 0)
break;
+ if (r == 0)
+ break;
k = dns_label_undo_idna(label, r, label, sizeof(label));
if (k < 0)
@@ -486,13 +502,9 @@ void dns_name_hash_func(const void *s, struct siphash *state) {
if (k > 0)
r = k;
- if (r == 0)
- break;
-
- label[r] = 0;
- ascii_strlower(label);
-
- string_hash_func(label, state);
+ ascii_strlower_n(label, r);
+ siphash24_compress(label, r, state);
+ siphash24_compress_byte(0, state); /* make sure foobar and foo.bar result in different hashes */
}
/* enforce that all names are terminated by the empty label */
@@ -527,7 +539,7 @@ int dns_name_compare_func(const void *a, const void *b) {
if (k > 0)
r = k;
if (w > 0)
- r = w;
+ q = w;
la[r] = lb[q] = 0;
r = strcasecmp(la, lb);
@@ -899,19 +911,11 @@ int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, boo
if (r < 0)
return r;
- if (canonical) {
- size_t i;
-
- /* Optionally, output the name in DNSSEC
- * canonical format, as described in RFC 4034,
- * section 6.2. Or in other words: in
- * lower-case. */
-
- for (i = 0; i < (size_t) r; i++) {
- if (out[i] >= 'A' && out[i] <= 'Z')
- out[i] = out[i] - 'A' + 'a';
- }
- }
+ /* Optionally, output the name in DNSSEC canonical
+ * format, as described in RFC 4034, section 6.2. Or
+ * in other words: in lower-case. */
+ if (canonical)
+ ascii_strlower_n((char*) out, (size_t) r);
/* Fill label length, move forward */
*label_length = r;
@@ -1153,3 +1157,77 @@ finish:
return 0;
}
+
+int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) {
+ const char* labels[DNS_N_LABELS_MAX+1];
+ unsigned n = 0;
+ const char *p;
+ int r;
+
+ assert(name);
+ assert(ret);
+
+ p = name;
+ for (;;) {
+ if (n > DNS_N_LABELS_MAX)
+ return -EINVAL;
+
+ labels[n] = p;
+
+ r = dns_name_parent(&p);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ n++;
+ }
+
+ if (n < n_labels)
+ return -EINVAL;
+
+ *ret = labels[n - n_labels];
+ return (int) (n - n_labels);
+}
+
+int dns_name_count_labels(const char *name) {
+ unsigned n = 0;
+ const char *p;
+ int r;
+
+ assert(name);
+
+ p = name;
+ for (;;) {
+ r = dns_name_parent(&p);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ if (n >= DNS_N_LABELS_MAX)
+ return -EINVAL;
+
+ n++;
+ }
+
+ return (int) n;
+}
+
+int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b) {
+ int r;
+
+ assert(a);
+ assert(b);
+
+ while (n_labels > 0) {
+
+ r = dns_name_parent(&a);
+ if (r <= 0)
+ return r;
+
+ n_labels --;
+ }
+
+ return dns_name_equal(a, b);
+}
diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h
index 3f8f621802..dd8ae3ac98 100644
--- a/src/shared/dns-domain.h
+++ b/src/shared/dns-domain.h
@@ -42,11 +42,18 @@
/* Maximum length of a full hostname, on the wire, including the final NUL byte */
#define DNS_WIRE_FOMAT_HOSTNAME_MAX 255
+/* Maximum number of labels per valid hostname */
+#define DNS_N_LABELS_MAX 127
+
int dns_label_unescape(const char **name, char *dest, size_t sz);
int dns_label_unescape_suffix(const char *name, const char **label_end, char *dest, size_t sz);
int dns_label_escape(const char *p, size_t l, char *dest, size_t sz);
int dns_label_escape_new(const char *p, size_t l, char **ret);
+static inline int dns_name_parent(const char **name) {
+ return dns_label_unescape(name, NULL, DNS_LABEL_MAX);
+}
+
int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max);
int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max);
@@ -92,3 +99,8 @@ bool dns_service_name_is_valid(const char *name);
int dns_service_join(const char *name, const char *type, const char *domain, char **ret);
int dns_service_split(const char *joined, char **name, char **type, char **domain);
+
+int dns_name_suffix(const char *name, unsigned n_labels, const char **ret);
+int dns_name_count_labels(const char *name);
+
+int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b);
diff --git a/src/shared/generator.c b/src/shared/generator.c
index 37de3f7cb1..76808cbdd5 100644
--- a/src/shared/generator.c
+++ b/src/shared/generator.c
@@ -190,7 +190,6 @@ int generator_write_timeouts(
return write_drop_in_format(dir, unit, 50, "device-timeout",
"# Automatically generated by %s\n\n"
- "[Unit]\nJobTimeoutSec=" USEC_FMT,
- program_invocation_short_name,
- u / USEC_PER_SEC);
+ "[Unit]\nJobTimeoutSec=%s",
+ program_invocation_short_name, timeout);
}
diff --git a/src/shared/gpt.h b/src/shared/gpt.h
index add1df420f..5f4c00ba83 100644
--- a/src/shared/gpt.h
+++ b/src/shared/gpt.h
@@ -25,7 +25,7 @@
#include "sd-id128.h"
-/* We only support root disk discovery for x86, x86-64 and ARM for
+/* We only support root disk discovery for x86, x86-64, Itanium and ARM for
* now, since EFI for anything else doesn't really exist, and we only
* care for root partitions on the same disk as the EFI ESP. */
@@ -33,6 +33,7 @@
#define GPT_ROOT_X86_64 SD_ID128_MAKE(4f,68,bc,e3,e8,cd,4d,b1,96,e7,fb,ca,f9,84,b7,09)
#define GPT_ROOT_ARM SD_ID128_MAKE(69,da,d7,10,2c,e4,4e,3c,b1,6c,21,a1,d4,9a,be,d3)
#define GPT_ROOT_ARM_64 SD_ID128_MAKE(b9,21,b0,45,1d,f0,41,c3,af,44,4c,6f,28,0d,3f,ae)
+#define GPT_ROOT_IA64 SD_ID128_MAKE(99,3d,8d,3d,f8,0e,42,25,85,5a,9d,af,8e,d7,ea,97)
#define GPT_ESP SD_ID128_MAKE(c1,2a,73,28,f8,1f,11,d2,ba,4b,00,a0,c9,3e,c9,3b)
#define GPT_SWAP SD_ID128_MAKE(06,57,fd,6d,a4,ab,43,c4,84,e5,09,33,c8,4b,4f,4f)
@@ -46,6 +47,10 @@
# define GPT_ROOT_NATIVE GPT_ROOT_X86
#endif
+#if defined(__ia64__)
+# define GPT_ROOT_NATIVE GPT_ROOT_IA64
+#endif
+
#if defined(__aarch64__) && (__BYTE_ORDER != __BIG_ENDIAN)
# define GPT_ROOT_NATIVE GPT_ROOT_ARM_64
# define GPT_ROOT_SECONDARY GPT_ROOT_ARM
diff --git a/src/shared/import-util.c b/src/shared/import-util.c
index 29ce732b56..ad400e8693 100644
--- a/src/shared/import-util.c
+++ b/src/shared/import-util.c
@@ -160,58 +160,6 @@ int raw_strip_suffixes(const char *p, char **ret) {
return 0;
}
-bool dkr_digest_is_valid(const char *digest) {
- /* 7 chars for prefix, 64 chars for the digest itself */
- if (strlen(digest) != 71)
- return false;
-
- return startswith(digest, "sha256:") && in_charset(digest + 7, "0123456789abcdef");
-}
-
-bool dkr_ref_is_valid(const char *ref) {
- const char *colon;
-
- if (isempty(ref))
- return false;
-
- colon = strchr(ref, ':');
- if (!colon)
- return filename_is_valid(ref);
-
- return dkr_digest_is_valid(ref);
-}
-
-bool dkr_name_is_valid(const char *name) {
- const char *slash, *p;
-
- if (isempty(name))
- return false;
-
- slash = strchr(name, '/');
- if (!slash)
- return false;
-
- if (!filename_is_valid(slash + 1))
- return false;
-
- p = strndupa(name, slash - name);
- if (!filename_is_valid(p))
- return false;
-
- return true;
-}
-
-bool dkr_id_is_valid(const char *id) {
-
- if (!filename_is_valid(id))
- return false;
-
- if (!in_charset(id, "0123456789abcdef"))
- return false;
-
- return true;
-}
-
int import_assign_pool_quota_and_warn(const char *path) {
int r;
diff --git a/src/shared/import-util.h b/src/shared/import-util.h
index 9120a5119f..4bfa2d9aae 100644
--- a/src/shared/import-util.h
+++ b/src/shared/import-util.h
@@ -42,10 +42,4 @@ ImportVerify import_verify_from_string(const char *s) _pure_;
int tar_strip_suffixes(const char *name, char **ret);
int raw_strip_suffixes(const char *name, char **ret);
-bool dkr_name_is_valid(const char *name);
-bool dkr_id_is_valid(const char *id);
-bool dkr_ref_is_valid(const char *ref);
-bool dkr_digest_is_valid(const char *digest);
-#define dkr_tag_is_valid(tag) filename_is_valid(tag)
-
int import_assign_pool_quota_and_warn(const char *path);
diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c
index 193dad1943..a1f65d1a88 100644
--- a/src/shared/logs-show.c
+++ b/src/shared/logs-show.c
@@ -435,8 +435,9 @@ static int output_verbose(
r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &value, &size);
if (r < 0)
- log_debug_errno(r, "_SOURCE_REALTIME_TIMESTAMP invalid: %m");
+ return r;
else {
+ assert(r > 0);
r = safe_atou64(value, &realtime);
if (r < 0)
log_debug_errno(r, "Failed to parse realtime timestamp: %m");
diff --git a/src/shared/resolve-util.c b/src/shared/resolve-util.c
new file mode 100644
index 0000000000..bf6fc26841
--- /dev/null
+++ b/src/shared/resolve-util.c
@@ -0,0 +1,41 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2016 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "conf-parser.h"
+#include "resolve-util.h"
+#include "string-table.h"
+
+DEFINE_CONFIG_PARSE_ENUM(config_parse_resolve_support, resolve_support, ResolveSupport, "Failed to parse resolve support setting");
+DEFINE_CONFIG_PARSE_ENUM(config_parse_dnssec_mode, dnssec_mode, DnssecMode, "Failed to parse DNSSEC mode setting");
+
+static const char* const resolve_support_table[_RESOLVE_SUPPORT_MAX] = {
+ [RESOLVE_SUPPORT_NO] = "no",
+ [RESOLVE_SUPPORT_YES] = "yes",
+ [RESOLVE_SUPPORT_RESOLVE] = "resolve",
+};
+DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(resolve_support, ResolveSupport, RESOLVE_SUPPORT_YES);
+
+static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = {
+ [DNSSEC_NO] = "no",
+ [DNSSEC_ALLOW_DOWNGRADE] = "allow-downgrade",
+ [DNSSEC_YES] = "yes",
+};
+DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dnssec_mode, DnssecMode, DNSSEC_YES);
diff --git a/src/shared/resolve-util.h b/src/shared/resolve-util.h
new file mode 100644
index 0000000000..fd93a13f73
--- /dev/null
+++ b/src/shared/resolve-util.h
@@ -0,0 +1,62 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2016 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "macro.h"
+
+typedef enum ResolveSupport ResolveSupport;
+typedef enum DnssecMode DnssecMode;
+
+enum ResolveSupport {
+ RESOLVE_SUPPORT_NO,
+ RESOLVE_SUPPORT_YES,
+ RESOLVE_SUPPORT_RESOLVE,
+ _RESOLVE_SUPPORT_MAX,
+ _RESOLVE_SUPPORT_INVALID = -1
+};
+
+enum DnssecMode {
+ /* No DNSSEC validation is done */
+ DNSSEC_NO,
+
+ /* Validate locally, if the server knows DO, but if not,
+ * don't. Don't trust the AD bit. If the server doesn't do
+ * DNSSEC properly, downgrade to non-DNSSEC operation. Of
+ * course, we then are vulnerable to a downgrade attack, but
+ * that's life and what is configured. */
+ DNSSEC_ALLOW_DOWNGRADE,
+
+ /* Insist on DNSSEC server support, and rather fail than downgrading. */
+ DNSSEC_YES,
+
+ _DNSSEC_MODE_MAX,
+ _DNSSEC_MODE_INVALID = -1
+};
+
+int config_parse_resolve_support(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_dnssec_mode(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);
+
+const char* resolve_support_to_string(ResolveSupport p) _const_;
+ResolveSupport resolve_support_from_string(const char *s) _pure_;
+
+const char* dnssec_mode_to_string(DnssecMode p) _const_;
+DnssecMode dnssec_mode_from_string(const char *s) _pure_;
diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c
index b1bbbdaadd..bf0739e5fa 100644
--- a/src/shared/switch-root.c
+++ b/src/shared/switch-root.c
@@ -35,6 +35,7 @@
#include "mkdir.h"
#include "path-util.h"
#include "rm-rf.h"
+#include "stdio-util.h"
#include "string-util.h"
#include "switch-root.h"
#include "user-util.h"
@@ -77,7 +78,7 @@ int switch_root(const char *new_root, const char *oldroot, bool detach_oldroot,
char new_mount[PATH_MAX];
struct stat sb;
- snprintf(new_mount, sizeof(new_mount), "%s%s", new_root, i);
+ xsprintf(new_mount, "%s%s", new_root, i);
mkdir_p_label(new_mount, 0755);
diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h
index 072832a916..1183df6105 100644
--- a/src/systemd/sd-messages.h
+++ b/src/systemd/sd-messages.h
@@ -86,6 +86,9 @@ _SD_BEGIN_DECLARATIONS;
#define SD_MESSAGE_BOOTCHART SD_ID128_MAKE(9f,26,aa,56,2c,f4,40,c2,b1,6c,77,3d,04,79,b5,18)
+#define SD_MESSAGE_DNSSEC_FAILURE SD_ID128_MAKE(16,75,d7,f1,72,17,40,98,b1,10,8b,f8,c7,dc,8f,5d)
+#define SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED SD_ID128_MAKE(4d,44,08,cf,d0,d1,44,85,91,84,d1,e6,5d,7c,8a,65)
+
_SD_END_DECLARATIONS;
#endif
diff --git a/src/systemd/sd-network.h b/src/systemd/sd-network.h
index 79b4bf9ea3..653c61a162 100644
--- a/src/systemd/sd-network.h
+++ b/src/systemd/sd-network.h
@@ -111,6 +111,27 @@ int sd_network_link_get_ntp(int ifindex, char ***addr);
*/
int sd_network_link_get_llmnr(int ifindex, char **llmnr);
+/* Indicates whether or not MulticastDNS should be enabled for the
+ * link.
+ * Possible levels of support: yes, no, resolve
+ * Possible return codes:
+ * -ENODATA: networkd is not aware of the link
+ */
+int sd_network_link_get_mdns(int ifindex, char **mdns);
+
+/* Indicates whether or not DNSSEC should be enabled for the link
+ * Possible levels of support: yes, no, allow-downgrade
+ * Possible return codes:
+ * -ENODATA: networkd is not aware of the link
+ */
+int sd_network_link_get_dnssec(int ifindex, char **dnssec);
+
+/* Returns the list of per-interface DNSSEC negative trust anchors
+ * Possible return codes:
+ * -ENODATA: networkd is not aware of the link, or has no such data
+ */
+int sd_network_link_get_dnssec_negative_trust_anchors(int ifindex, char ***nta);
+
int sd_network_link_get_lldp(int ifindex, char **lldp);
/* Get the DNS domain names for a given link. */
diff --git a/src/test/test-capability.c b/src/test/test-capability.c
index fc8d3ffe0d..629bb63c81 100644
--- a/src/test/test-capability.c
+++ b/src/test/test-capability.c
@@ -20,6 +20,7 @@
#include <netinet/in.h>
#include <pwd.h>
#include <sys/capability.h>
+#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
@@ -66,8 +67,9 @@ static void show_capabilities(void) {
cap_free(text);
}
-static int setup_tests(void) {
+static int setup_tests(bool *run_ambient) {
struct passwd *nobody;
+ int r;
nobody = getpwnam("nobody");
if (!nobody) {
@@ -77,6 +79,18 @@ static int setup_tests(void) {
test_uid = nobody->pw_uid;
test_gid = nobody->pw_gid;
+ *run_ambient = false;
+
+ r = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0);
+
+ /* There's support for PR_CAP_AMBIENT if the prctl() call
+ * succeeded or error code was something else than EINVAL. The
+ * EINVAL check should be good enough to rule out false
+ * positives. */
+
+ if (r >= 0 || errno != EINVAL)
+ *run_ambient = true;
+
return 0;
}
@@ -140,8 +154,53 @@ static void test_have_effective_cap(void) {
assert_se(!have_effective_cap(CAP_CHOWN));
}
+static void test_update_inherited_set(void) {
+ cap_t caps;
+ uint64_t set = 0;
+ cap_flag_value_t fv;
+
+ caps = cap_get_proc();
+ assert_se(caps);
+ assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv));
+ assert(fv == CAP_CLEAR);
+
+ set = (UINT64_C(1) << CAP_CHOWN);
+
+ assert_se(!capability_update_inherited_set(caps, set));
+ assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv));
+ assert(fv == CAP_SET);
+
+ cap_free(caps);
+}
+
+static void test_set_ambient_caps(void) {
+ cap_t caps;
+ uint64_t set = 0;
+ cap_flag_value_t fv;
+
+ caps = cap_get_proc();
+ assert_se(caps);
+ assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv));
+ assert(fv == CAP_CLEAR);
+ cap_free(caps);
+
+ assert_se(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_CHOWN, 0, 0) == 0);
+
+ set = (UINT64_C(1) << CAP_CHOWN);
+
+ assert_se(!capability_ambient_set_apply(set, true));
+
+ caps = cap_get_proc();
+ assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv));
+ assert(fv == CAP_SET);
+ cap_free(caps);
+
+ assert_se(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_CHOWN, 0, 0) == 1);
+}
+
int main(int argc, char *argv[]) {
int r;
+ bool run_ambient;
log_parse_environment();
log_open();
@@ -149,14 +208,19 @@ int main(int argc, char *argv[]) {
if (getuid() != 0)
return EXIT_TEST_SKIP;
- r = setup_tests();
+ r = setup_tests(&run_ambient);
if (r < 0)
return -r;
show_capabilities();
test_drop_privileges();
+ test_update_inherited_set();
+
fork_test(test_have_effective_cap);
+ if (run_ambient)
+ fork_test(test_set_ambient_caps);
+
return 0;
}
diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c
index de003e251c..6c3c49908f 100644
--- a/src/test/test-dns-domain.c
+++ b/src/test/test-dns-domain.c
@@ -140,9 +140,9 @@ static void test_dns_label_unescape_suffix(void) {
test_dns_label_unescape_suffix_one("hallo\\", "hallo", "hallo", 20, -EINVAL, -EINVAL);
test_dns_label_unescape_suffix_one("hallo\\032 ", "hallo ", "", 20, 7, 0);
test_dns_label_unescape_suffix_one(".", "", "", 20, 0, 0);
- test_dns_label_unescape_suffix_one("..", "", "", 20, 0, 0);
+ test_dns_label_unescape_suffix_one("..", "", "", 20, 0, -EINVAL);
test_dns_label_unescape_suffix_one(".foobar", "foobar", "", 20, 6, -EINVAL);
- test_dns_label_unescape_suffix_one("foobar.", "", "foobar", 20, 0, 6);
+ test_dns_label_unescape_suffix_one("foobar.", "foobar", "", 20, 6, 0);
test_dns_label_unescape_suffix_one("foo\\\\bar", "foo\\bar", "", 20, 7, 0);
test_dns_label_unescape_suffix_one("foo.bar", "bar", "foo", 20, 3, 3);
test_dns_label_unescape_suffix_one("foo..bar", "bar", "", 20, 3, -EINVAL);
@@ -168,7 +168,7 @@ static void test_dns_label_escape_one(const char *what, size_t l, const char *ex
static void test_dns_label_escape(void) {
test_dns_label_escape_one("", 0, NULL, -EINVAL);
test_dns_label_escape_one("hallo", 5, "hallo", 5);
- test_dns_label_escape_one("hallo", 6, NULL, -EINVAL);
+ test_dns_label_escape_one("hallo", 6, "hallo\\000", 9);
test_dns_label_escape_one("hallo hallo.foobar,waldi", 24, "hallo\\032hallo\\.foobar\\044waldi", 31);
}
@@ -190,7 +190,7 @@ static void test_dns_name_normalize(void) {
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("\\000", "\\000", 0);
test_dns_name_normalize_one("..", NULL, -EINVAL);
test_dns_name_normalize_one(".foobar", NULL, -EINVAL);
test_dns_name_normalize_one("foobar.", "foobar", 0);
@@ -216,7 +216,7 @@ static void test_dns_name_equal(void) {
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("\\000", "\\000", true);
test_dns_name_equal_one(".", "", true);
test_dns_name_equal_one(".", ".", true);
test_dns_name_equal_one("..", "..", -EINVAL);
@@ -475,6 +475,90 @@ static void test_dns_name_change_suffix(void) {
test_dns_name_change_suffix_one("a", "b", "c", 0, NULL);
}
+static void test_dns_name_suffix_one(const char *name, unsigned n_labels, const char *result, int ret) {
+ const char *p = NULL;
+
+ assert_se(ret == dns_name_suffix(name, n_labels, &p));
+ assert_se(streq_ptr(p, result));
+}
+
+static void test_dns_name_suffix(void) {
+ test_dns_name_suffix_one("foo.bar", 2, "foo.bar", 0);
+ test_dns_name_suffix_one("foo.bar", 1, "bar", 1);
+ test_dns_name_suffix_one("foo.bar", 0, "", 2);
+ test_dns_name_suffix_one("foo.bar", 3, NULL, -EINVAL);
+ test_dns_name_suffix_one("foo.bar", 4, NULL, -EINVAL);
+
+ test_dns_name_suffix_one("bar", 1, "bar", 0);
+ test_dns_name_suffix_one("bar", 0, "", 1);
+ test_dns_name_suffix_one("bar", 2, NULL, -EINVAL);
+ test_dns_name_suffix_one("bar", 3, NULL, -EINVAL);
+
+ test_dns_name_suffix_one("", 0, "", 0);
+ test_dns_name_suffix_one("", 1, NULL, -EINVAL);
+ test_dns_name_suffix_one("", 2, NULL, -EINVAL);
+}
+
+static void test_dns_name_count_labels_one(const char *name, int n) {
+ assert_se(dns_name_count_labels(name) == n);
+}
+
+static void test_dns_name_count_labels(void) {
+ test_dns_name_count_labels_one("foo.bar.quux.", 3);
+ test_dns_name_count_labels_one("foo.bar.quux", 3);
+ test_dns_name_count_labels_one("foo.bar.", 2);
+ test_dns_name_count_labels_one("foo.bar", 2);
+ test_dns_name_count_labels_one("foo.", 1);
+ test_dns_name_count_labels_one("foo", 1);
+ test_dns_name_count_labels_one("", 0);
+ test_dns_name_count_labels_one(".", 0);
+ test_dns_name_count_labels_one("..", -EINVAL);
+}
+
+static void test_dns_name_equal_skip_one(const char *a, unsigned n_labels, const char *b, int ret) {
+ assert_se(dns_name_equal_skip(a, n_labels, b) == ret);
+}
+
+static void test_dns_name_equal_skip(void) {
+ test_dns_name_equal_skip_one("foo", 0, "bar", 0);
+ test_dns_name_equal_skip_one("foo", 0, "foo", 1);
+ test_dns_name_equal_skip_one("foo", 1, "foo", 0);
+ test_dns_name_equal_skip_one("foo", 2, "foo", 0);
+
+ test_dns_name_equal_skip_one("foo.bar", 0, "foo.bar", 1);
+ test_dns_name_equal_skip_one("foo.bar", 1, "foo.bar", 0);
+ test_dns_name_equal_skip_one("foo.bar", 2, "foo.bar", 0);
+ test_dns_name_equal_skip_one("foo.bar", 3, "foo.bar", 0);
+
+ test_dns_name_equal_skip_one("foo.bar", 0, "bar", 0);
+ test_dns_name_equal_skip_one("foo.bar", 1, "bar", 1);
+ test_dns_name_equal_skip_one("foo.bar", 2, "bar", 0);
+ test_dns_name_equal_skip_one("foo.bar", 3, "bar", 0);
+
+ test_dns_name_equal_skip_one("foo.bar", 0, "", 0);
+ test_dns_name_equal_skip_one("foo.bar", 1, "", 0);
+ test_dns_name_equal_skip_one("foo.bar", 2, "", 1);
+ test_dns_name_equal_skip_one("foo.bar", 3, "", 0);
+
+ test_dns_name_equal_skip_one("", 0, "", 1);
+ test_dns_name_equal_skip_one("", 1, "", 0);
+ test_dns_name_equal_skip_one("", 1, "foo", 0);
+ test_dns_name_equal_skip_one("", 2, "foo", 0);
+}
+
+static void test_dns_name_compare_func(void) {
+ assert_se(dns_name_compare_func("", "") == 0);
+ assert_se(dns_name_compare_func("", ".") == 0);
+ assert_se(dns_name_compare_func(".", "") == 0);
+ assert_se(dns_name_compare_func("foo", "foo.") == 0);
+ assert_se(dns_name_compare_func("foo.", "foo") == 0);
+ assert_se(dns_name_compare_func("foo", "foo") == 0);
+ assert_se(dns_name_compare_func("foo.", "foo.") == 0);
+ assert_se(dns_name_compare_func("heise.de", "HEISE.DE.") == 0);
+
+ assert_se(dns_name_compare_func("de.", "heise.de") != 0);
+}
+
int main(int argc, char *argv[]) {
test_dns_label_unescape();
@@ -495,6 +579,10 @@ int main(int argc, char *argv[]) {
test_dns_service_join();
test_dns_service_split();
test_dns_name_change_suffix();
+ test_dns_name_suffix();
+ test_dns_name_count_labels();
+ test_dns_name_equal_skip();
+ test_dns_name_compare_func();
return 0;
}
diff --git a/src/test/test-execute.c b/src/test/test-execute.c
index 753afadb0a..92857cb5e2 100644
--- a/src/test/test-execute.c
+++ b/src/test/test-execute.c
@@ -20,6 +20,7 @@
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
+#include <sys/prctl.h>
#include <sys/types.h>
#include "fileio.h"
@@ -224,6 +225,20 @@ static void test_exec_capabilityboundingset(Manager *m) {
test(m, "exec-capabilityboundingset-invert.service", 0, CLD_EXITED);
}
+static void test_exec_capabilityambientset(Manager *m) {
+ int r;
+
+ /* Check if the kernel has support for ambient capabilities. Run
+ * the tests only if that's the case. Clearing all ambient
+ * capabilities is fine, since we are expecting them to be unset
+ * in the first place for the tests. */
+ r = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0);
+ if (r >= 0 || errno != EINVAL) {
+ test(m, "exec-capabilityambientset.service", 0, CLD_EXITED);
+ test(m, "exec-capabilityambientset-merge.service", 0, CLD_EXITED);
+ }
+}
+
static void test_exec_privatenetwork(Manager *m) {
int r;
@@ -266,6 +281,7 @@ int main(int argc, char *argv[]) {
test_exec_umask,
test_exec_runtimedirectory,
test_exec_capabilityboundingset,
+ test_exec_capabilityambientset,
test_exec_oomscoreadjust,
test_exec_ioschedulingclass,
NULL,
diff --git a/src/test/test-libudev.c b/src/test/test-libudev.c
index 350eaf734d..94d852b3b0 100644
--- a/src/test/test-libudev.c
+++ b/src/test/test-libudev.c
@@ -25,6 +25,7 @@
#include "libudev.h"
+#include "stdio-util.h"
#include "string-util.h"
#include "udev-util.h"
#include "util.h"
@@ -460,7 +461,7 @@ int main(int argc, char *argv[]) {
/* add sys path if needed */
if (!startswith(syspath, "/sys")) {
- snprintf(path, sizeof(path), "/sys/%s", syspath);
+ xsprintf(path, "/sys/%s", syspath);
syspath = path;
}
diff --git a/src/test/test-rlimit-util.c b/src/test/test-rlimit-util.c
index 00d3ecc0de..24bfe7a60e 100644
--- a/src/test/test-rlimit-util.c
+++ b/src/test/test-rlimit-util.c
@@ -37,7 +37,7 @@ int main(int argc, char *argv[]) {
assert_se(getrlimit(RLIMIT_NOFILE, &old) == 0);
new.rlim_cur = MIN(5U, old.rlim_max);
- new.rlim_max = MIN(10U, old.rlim_max);
+ new.rlim_max = old.rlim_max;
assert_se(setrlimit(RLIMIT_NOFILE, &new) >= 0);
assert_se(rlimit_from_string("LimitNOFILE") == RLIMIT_NOFILE);
@@ -53,7 +53,7 @@ int main(int argc, char *argv[]) {
assert_se(old.rlim_max == new.rlim_max);
assert_se(getrlimit(RLIMIT_NOFILE, &old) == 0);
- high = RLIMIT_MAKE_CONST(old.rlim_max + 1);
+ high = RLIMIT_MAKE_CONST(old.rlim_max == RLIM_INFINITY ? old.rlim_max : old.rlim_max + 1);
assert_se(setrlimit_closest(RLIMIT_NOFILE, &high) == 0);
assert_se(getrlimit(RLIMIT_NOFILE, &new) == 0);
assert_se(new.rlim_max == old.rlim_max);
diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c
index 0b3630f77c..cd1e4e4698 100644
--- a/src/test/test-unit-file.c
+++ b/src/test/test-unit-file.c
@@ -28,6 +28,7 @@
#include <unistd.h>
#include "alloc-util.h"
+#include "capability-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "hashmap.h"
@@ -625,8 +626,8 @@ static uint64_t make_cap(int cap) {
return ((uint64_t) 1ULL << (uint64_t) cap);
}
-static void test_config_parse_bounding_set(void) {
- /* int config_parse_bounding_set(
+static void test_config_parse_capability_set(void) {
+ /* int config_parse_capability_set(
const char *unit,
const char *filename,
unsigned line,
@@ -638,38 +639,38 @@ static void test_config_parse_bounding_set(void) {
void *data,
void *userdata) */
int r;
- uint64_t capability_bounding_set_drop = 0;
+ uint64_t capability_bounding_set = 0;
- r = config_parse_bounding_set(NULL, "fake", 1, "section", 1,
+ r = config_parse_capability_set(NULL, "fake", 1, "section", 1,
"CapabilityBoundingSet", 0, "CAP_NET_RAW",
- &capability_bounding_set_drop, NULL);
+ &capability_bounding_set, NULL);
assert_se(r >= 0);
- assert_se(capability_bounding_set_drop == ~make_cap(CAP_NET_RAW));
+ assert_se(capability_bounding_set == make_cap(CAP_NET_RAW));
- r = config_parse_bounding_set(NULL, "fake", 1, "section", 1,
+ r = config_parse_capability_set(NULL, "fake", 1, "section", 1,
"CapabilityBoundingSet", 0, "CAP_NET_ADMIN",
- &capability_bounding_set_drop, NULL);
+ &capability_bounding_set, NULL);
assert_se(r >= 0);
- assert_se(capability_bounding_set_drop == ~(make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN)));
+ assert_se(capability_bounding_set == (make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN)));
- r = config_parse_bounding_set(NULL, "fake", 1, "section", 1,
+ r = config_parse_capability_set(NULL, "fake", 1, "section", 1,
"CapabilityBoundingSet", 0, "",
- &capability_bounding_set_drop, NULL);
+ &capability_bounding_set, NULL);
assert_se(r >= 0);
- assert_se(capability_bounding_set_drop == ~((uint64_t) 0ULL));
+ assert_se(capability_bounding_set == UINT64_C(0));
- r = config_parse_bounding_set(NULL, "fake", 1, "section", 1,
+ r = config_parse_capability_set(NULL, "fake", 1, "section", 1,
"CapabilityBoundingSet", 0, "~",
- &capability_bounding_set_drop, NULL);
+ &capability_bounding_set, NULL);
assert_se(r >= 0);
- assert_se(capability_bounding_set_drop == (uint64_t) 0ULL);
+ assert_se(cap_test_all(capability_bounding_set));
- capability_bounding_set_drop = 0;
- r = config_parse_bounding_set(NULL, "fake", 1, "section", 1,
+ capability_bounding_set = 0;
+ r = config_parse_capability_set(NULL, "fake", 1, "section", 1,
"CapabilityBoundingSet", 0, " 'CAP_NET_RAW' WAT_CAP??? CAP_NET_ADMIN CAP'_trailing_garbage",
- &capability_bounding_set_drop, NULL);
+ &capability_bounding_set, NULL);
assert_se(r >= 0);
- assert_se(capability_bounding_set_drop == ~(make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN)));
+ assert_se(capability_bounding_set == (make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN)));
}
static void test_config_parse_rlimit(void) {
@@ -829,7 +830,7 @@ int main(int argc, char *argv[]) {
r = test_unit_file_get_set();
test_config_parse_exec();
- test_config_parse_bounding_set();
+ test_config_parse_capability_set();
test_config_parse_rlimit();
test_config_parse_pass_environ();
test_load_env_file_1();
diff --git a/src/udev/collect/collect.c b/src/udev/collect/collect.c
index b6c95cd452..349585b634 100644
--- a/src/udev/collect/collect.c
+++ b/src/udev/collect/collect.c
@@ -27,6 +27,7 @@
#include "alloc-util.h"
#include "libudev-private.h"
#include "macro.h"
+#include "stdio-util.h"
#include "string-util.h"
#define BUFSIZE 16
@@ -91,7 +92,7 @@ static int prepare(char *dir, char *filename)
if (r < 0 && errno != EEXIST)
return -errno;
- snprintf(buf, sizeof(buf), "%s/%s", dir, filename);
+ xsprintf(buf, "%s/%s", dir, filename);
fd = open(buf,O_RDWR|O_CREAT|O_CLOEXEC, S_IRUSR|S_IWUSR);
if (fd < 0)
diff --git a/src/udev/udev-builtin-input_id.c b/src/udev/udev-builtin-input_id.c
index 1d31829a08..691ef5656d 100644
--- a/src/udev/udev-builtin-input_id.c
+++ b/src/udev/udev-builtin-input_id.c
@@ -33,6 +33,7 @@
#include <linux/input.h>
#include "fd-util.h"
+#include "stdio-util.h"
#include "string-util.h"
#include "udev.h"
#include "util.h"
@@ -66,8 +67,8 @@ static void extract_info(struct udev_device *dev, const char *devpath, bool test
if (xabsinfo.resolution <= 0 || yabsinfo.resolution <= 0)
return;
- snprintf(width, sizeof(width), "%d", abs_size_mm(&xabsinfo));
- snprintf(height, sizeof(height), "%d", abs_size_mm(&yabsinfo));
+ xsprintf(width, "%d", abs_size_mm(&xabsinfo));
+ xsprintf(height, "%d", abs_size_mm(&yabsinfo));
udev_builtin_add_property(dev, test, "ID_INPUT_WIDTH_MM", width);
udev_builtin_add_property(dev, test, "ID_INPUT_HEIGHT_MM", height);
@@ -93,7 +94,7 @@ static void get_cap_mask(struct udev_device *dev,
if (!v)
v = "";
- snprintf(text, sizeof(text), "%s", v);
+ xsprintf(text, "%s", v);
log_debug("%s raw kernel attribute: %s", attr, text);
memzero(bitmask, bitmask_size);
@@ -115,7 +116,8 @@ static void get_cap_mask(struct udev_device *dev,
if (test) {
/* printf pattern with the right unsigned long number of hex chars */
- snprintf(text, sizeof(text), " bit %%4u: %%0%zulX\n", 2 * sizeof(unsigned long));
+ xsprintf(text, " bit %%4u: %%0%zulX\n",
+ 2 * sizeof(unsigned long));
log_debug("%s decoded bit map:", attr);
val = bitmask_size / sizeof (unsigned long);
/* skip over leading zeros */
@@ -205,12 +207,12 @@ static bool test_pointers(struct udev_device *dev,
/* This path is taken by VMware's USB mouse, which has
* absolute axes, but no touch/pressure button. */
is_mouse = true;
- else if (has_touch)
+ else if (has_touch || is_direct)
is_touchscreen = true;
else if (has_joystick_axes_or_buttons)
is_joystick = true;
}
- if (has_mt_coordinates && is_direct)
+ if (has_mt_coordinates && (is_direct || has_touch))
is_touchscreen = true;
if (has_rel_coordinates && has_mouse_button)
diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c
index e549fdbee9..e83b8b1c12 100644
--- a/src/udev/udev-builtin-net_id.c
+++ b/src/udev/udev-builtin-net_id.c
@@ -102,6 +102,7 @@
#include "fd-util.h"
#include "fileio.h"
+#include "stdio-util.h"
#include "string-util.h"
#include "udev.h"
@@ -228,7 +229,7 @@ static int dev_pci_slot(struct udev_device *dev, struct netnames *names) {
err = -ENOENT;
goto out;
}
- snprintf(slots, sizeof(slots), "%s/slots", udev_device_get_syspath(pci));
+ xsprintf(slots, "%s/slots", udev_device_get_syspath(pci));
dir = opendir(slots);
if (!dir) {
err = -errno;
@@ -247,7 +248,7 @@ static int dev_pci_slot(struct udev_device *dev, struct netnames *names) {
continue;
if (i < 1)
continue;
- snprintf(str, sizeof(str), "%s/%s/address", slots, dent->d_name);
+ xsprintf(str, "%s/%s/address", slots, dent->d_name);
if (read_one_line_file(str, &address) >= 0) {
/* match slot address with device by stripping the function */
if (strneq(address, udev_device_get_sysname(names->pcidev), strlen(address)))
@@ -380,7 +381,7 @@ static int names_bcma(struct udev_device *dev, struct netnames *names) {
return -EINVAL;
/* suppress the common core == 0 */
if (core > 0)
- snprintf(names->bcma_core, sizeof(names->bcma_core), "b%u", core);
+ xsprintf(names->bcma_core, "b%u", core);
names->type = NET_BCMA;
return 0;
@@ -469,9 +470,9 @@ static int ieee_oui(struct udev_device *dev, struct netnames *names, bool test)
/* skip commonly misused 00:00:00 (Xerox) prefix */
if (memcmp(names->mac, "\0\0\0", 3) == 0)
return -EINVAL;
- snprintf(str, sizeof(str), "OUI:%02X%02X%02X%02X%02X%02X",
- names->mac[0], names->mac[1], names->mac[2],
- names->mac[3], names->mac[4], names->mac[5]);
+ xsprintf(str, "OUI:%02X%02X%02X%02X%02X%02X", names->mac[0],
+ names->mac[1], names->mac[2], names->mac[3], names->mac[4],
+ names->mac[5]);
udev_builtin_hwdb_lookup(dev, NULL, str, NULL, test);
return 0;
}
@@ -523,7 +524,7 @@ static int builtin_net_id(struct udev_device *dev, int argc, char *argv[], bool
if (err >= 0 && names.mac_valid) {
char str[IFNAMSIZ];
- snprintf(str, sizeof(str), "%sx%02x%02x%02x%02x%02x%02x", prefix,
+ xsprintf(str, "%sx%02x%02x%02x%02x%02x%02x", prefix,
names.mac[0], names.mac[1], names.mac[2],
names.mac[3], names.mac[4], names.mac[5]);
udev_builtin_add_property(dev, test, "ID_NET_NAME_MAC", str);
diff --git a/src/udev/udev-node.c b/src/udev/udev-node.c
index 39ae2cc1b1..fd7936c2dc 100644
--- a/src/udev/udev-node.c
+++ b/src/udev/udev-node.c
@@ -31,6 +31,7 @@
#include "fs-util.h"
#include "selinux-util.h"
#include "smack-util.h"
+#include "stdio-util.h"
#include "string-util.h"
#include "udev.h"
@@ -348,9 +349,10 @@ void udev_node_add(struct udev_device *dev, bool apply,
return;
/* always add /dev/{block,char}/$major:$minor */
- snprintf(filename, sizeof(filename), "/dev/%s/%u:%u",
+ xsprintf(filename, "/dev/%s/%u:%u",
streq(udev_device_get_subsystem(dev), "block") ? "block" : "char",
- major(udev_device_get_devnum(dev)), minor(udev_device_get_devnum(dev)));
+ major(udev_device_get_devnum(dev)),
+ minor(udev_device_get_devnum(dev)));
node_symlink(dev, udev_device_get_devnode(dev), filename);
/* create/update symlinks, add symlinks to name index */
@@ -367,8 +369,9 @@ void udev_node_remove(struct udev_device *dev) {
link_update(dev, udev_list_entry_get_name(list_entry), false);
/* remove /dev/{block,char}/$major:$minor */
- snprintf(filename, sizeof(filename), "/dev/%s/%u:%u",
+ xsprintf(filename, "/dev/%s/%u:%u",
streq(udev_device_get_subsystem(dev), "block") ? "block" : "char",
- major(udev_device_get_devnum(dev)), minor(udev_device_get_devnum(dev)));
+ major(udev_device_get_devnum(dev)),
+ minor(udev_device_get_devnum(dev)));
unlink(filename);
}
diff --git a/src/udev/udev-watch.c b/src/udev/udev-watch.c
index 60de703706..c0f4973f93 100644
--- a/src/udev/udev-watch.c
+++ b/src/udev/udev-watch.c
@@ -26,6 +26,7 @@
#include <sys/inotify.h>
#include <unistd.h>
+#include "stdio-util.h"
#include "udev.h"
static int inotify_fd = -1;
@@ -105,7 +106,7 @@ void udev_watch_begin(struct udev *udev, struct udev_device *dev) {
return;
}
- snprintf(filename, sizeof(filename), "/run/udev/watch/%d", wd);
+ xsprintf(filename, "/run/udev/watch/%d", wd);
mkdir_parents(filename, 0755);
unlink(filename);
r = symlink(udev_device_get_id_filename(dev), filename);
@@ -129,7 +130,7 @@ void udev_watch_end(struct udev *udev, struct udev_device *dev) {
log_debug("removing watch on '%s'", udev_device_get_devnode(dev));
inotify_rm_watch(inotify_fd, wd);
- snprintf(filename, sizeof(filename), "/run/udev/watch/%d", wd);
+ xsprintf(filename, "/run/udev/watch/%d", wd);
unlink(filename);
udev_device_set_watch_handle(dev, -1);
@@ -143,7 +144,7 @@ struct udev_device *udev_watch_lookup(struct udev *udev, int wd) {
if (inotify_fd < 0 || wd < 0)
return NULL;
- snprintf(filename, sizeof(filename), "/run/udev/watch/%d", wd);
+ xsprintf(filename, "/run/udev/watch/%d", wd);
len = readlink(filename, device, sizeof(device));
if (len <= 0 || (size_t)len == sizeof(device))
return NULL;
diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c
index a5f4529cfd..622fbe9a6d 100644
--- a/src/vconsole/vconsole-setup.c
+++ b/src/vconsole/vconsole-setup.c
@@ -39,6 +39,7 @@
#include "log.h"
#include "process-util.h"
#include "signal-util.h"
+#include "stdio-util.h"
#include "string-util.h"
#include "terminal-util.h"
#include "util.h"
@@ -215,11 +216,11 @@ static void font_copy_to_all_vcs(int fd) {
continue;
/* skip non-allocated ttys */
- snprintf(vcname, sizeof(vcname), "/dev/vcs%i", i);
+ xsprintf(vcname, "/dev/vcs%i", i);
if (access(vcname, F_OK) < 0)
continue;
- snprintf(vcname, sizeof(vcname), "/dev/tty%i", i);
+ xsprintf(vcname, "/dev/tty%i", i);
vcfd = open_terminal(vcname, O_RDWR|O_CLOEXEC);
if (vcfd < 0)
continue;