diff options
Diffstat (limited to 'src')
44 files changed, 1583 insertions, 593 deletions
diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index 973413ff42..20890e3279 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -74,5 +74,6 @@ void cmsg_close_all(struct msghdr *mh); bool fdname_is_valid(const char *s); +/* Hint: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5 */ #define ERRNO_IS_DISCONNECT(r) \ - IN_SET(r, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE) + IN_SET(r, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE, ENETUNREACH) diff --git a/src/basic/missing.h b/src/basic/missing.h index c187afa287..6ed2133ed1 100644 --- a/src/basic/missing.h +++ b/src/basic/missing.h @@ -1149,3 +1149,14 @@ static inline key_serial_t request_key(const char *type, const char *description #ifndef PR_CAP_AMBIENT_CLEAR_ALL #define PR_CAP_AMBIENT_CLEAR_ALL 4 #endif + +/* The following two defines are actually available in the kernel headers for longer, but we define them here anyway, + * since that makes it easier to use them in conjunction with the glibc net/if.h header which conflicts with + * linux/if.h. */ +#ifndef IF_OPER_UNKNOWN +#define IF_OPER_UNKNOWN 0 +#endif + +#ifndef IF_OPER_UP +#define IF_OPER_UP 6 +#endif diff --git a/src/core/execute.c b/src/core/execute.c index d70ba2be17..b9de2617a9 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -814,8 +814,7 @@ static int setup_pam( _cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL; pam_handle_t *handle = NULL; sigset_t old_ss; - int pam_code = PAM_SUCCESS; - int err = 0; + int pam_code = PAM_SUCCESS, r; char **e = NULL; bool close_session = false; pid_t pam_pid = 0, parent_pid; @@ -832,8 +831,8 @@ static int setup_pam( * daemon. We do things this way to ensure that the main PID * of the daemon is the one we initially fork()ed. */ - err = barrier_create(&barrier); - if (err < 0) + r = barrier_create(&barrier); + if (r < 0) goto fail; if (log_get_max_level() < LOG_DEBUG) @@ -875,12 +874,13 @@ static int setup_pam( parent_pid = getpid(); pam_pid = fork(); - if (pam_pid < 0) + if (pam_pid < 0) { + r = -errno; goto fail; + } if (pam_pid == 0) { - int sig; - int r = EXIT_PAM; + int sig, ret = EXIT_PAM; /* The child's job is to reset the PAM session on * termination */ @@ -945,11 +945,11 @@ static int setup_pam( goto child_finish; } - r = 0; + ret = 0; child_finish: pam_end(handle, pam_code | flags); - _exit(r); + _exit(ret); } barrier_set_role(&barrier, BARRIER_PARENT); @@ -978,10 +978,9 @@ static int setup_pam( fail: if (pam_code != PAM_SUCCESS) { log_error("PAM failed: %s", pam_strerror(handle, pam_code)); - err = -EPERM; /* PAM errors do not map to errno */ - } else { - err = log_error_errno(err < 0 ? err : errno, "PAM failed: %m"); - } + r = -EPERM; /* PAM errors do not map to errno */ + } else + log_error_errno(r, "PAM failed: %m"); if (handle) { if (close_session) @@ -993,7 +992,7 @@ fail: strv_free(e); closelog(); - return err; + return r; } #endif diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c index e7003da9c1..cfe111fd91 100644 --- a/src/journal-remote/journal-remote.c +++ b/src/journal-remote/journal-remote.c @@ -150,7 +150,7 @@ static int spawn_curl(const char* url) { return r; } -static int spawn_getter(const char *getter, const char *url) { +static int spawn_getter(const char *getter) { int r; _cleanup_strv_free_ char **words = NULL; @@ -159,10 +159,6 @@ static int spawn_getter(const char *getter, const char *url) { if (r < 0) return log_error_errno(r, "Failed to split getter option: %m"); - r = strv_extend(&words, url); - if (r < 0) - return log_error_errno(r, "Failed to create command line: %m"); - r = spawn_child(words[0], words); if (r < 0) log_error_errno(r, "Failed to spawn getter %s: %m", getter); @@ -897,18 +893,32 @@ static int remoteserver_init(RemoteServer *s, fd); } - if (arg_url) { - const char *url, *hostname; + if (arg_getter) { + log_info("Spawning getter %s...", arg_getter); + fd = spawn_getter(arg_getter); + if (fd < 0) + return fd; + + r = add_source(s, fd, (char*) arg_output, false); + if (r < 0) + return r; + } - url = strjoina(arg_url, "/entries"); + if (arg_url) { + const char *url; + char *hostname, *p; - if (arg_getter) { - log_info("Spawning getter %s...", url); - fd = spawn_getter(arg_getter, url); - } else { - log_info("Spawning curl %s...", url); - fd = spawn_curl(url); + if (!strstr(arg_url, "/entries")) { + if (endswith(arg_url, "/")) + url = strjoina(arg_url, "entries"); + else + url = strjoina(arg_url, "/entries"); } + else + url = strdupa(arg_url); + + log_info("Spawning curl %s...", url); + fd = spawn_curl(url); if (fd < 0) return fd; @@ -917,7 +927,13 @@ static int remoteserver_init(RemoteServer *s, startswith(arg_url, "http://") ?: arg_url; - r = add_source(s, fd, (char*) hostname, false); + hostname = strdupa(hostname); + if ((p = strchr(hostname, '/'))) + *p = '\0'; + if ((p = strchr(hostname, ':'))) + *p = '\0'; + + r = add_source(s, fd, hostname, false); if (r < 0) return r; } diff --git a/src/journal/coredump.c b/src/journal/coredump.c index 869c8fea03..7df59fe29b 100644 --- a/src/journal/coredump.c +++ b/src/journal/coredump.c @@ -297,7 +297,8 @@ static int save_external_coredump( const char *info[_INFO_LEN], uid_t uid, char **ret_filename, - int *ret_fd, + int *ret_node_fd, + int *ret_data_fd, uint64_t *ret_size) { _cleanup_free_ char *fn = NULL, *tmp = NULL; @@ -307,7 +308,8 @@ static int save_external_coredump( assert(info); assert(ret_filename); - assert(ret_fd); + assert(ret_node_fd); + assert(ret_data_fd); assert(ret_size); r = make_filename(info, &fn); @@ -386,11 +388,12 @@ static int save_external_coredump( unlink_noerrno(tmp); *ret_filename = fn_compressed; /* compressed */ - *ret_fd = fd; /* uncompressed */ + *ret_node_fd = fd_compressed; /* compressed */ + *ret_data_fd = fd; /* uncompressed */ *ret_size = (uint64_t) st.st_size; /* uncompressed */ fn_compressed = NULL; - fd = -1; + fd = fd_compressed = -1; return 0; @@ -400,12 +403,14 @@ static int save_external_coredump( uncompressed: #endif + r = fix_permissions(fd, tmp, fn, info, uid); if (r < 0) goto fail; *ret_filename = fn; - *ret_fd = fd; + *ret_data_fd = fd; + *ret_node_fd = -1; *ret_size = (uint64_t) st.st_size; fn = NULL; @@ -554,7 +559,7 @@ int main(int argc, char* argv[]) { _cleanup_free_ char *exe = NULL, *comm = NULL, *filename = NULL; const char *info[_INFO_LEN]; - _cleanup_close_ int coredump_fd = -1; + _cleanup_close_ int coredump_fd = -1, coredump_node_fd = -1; struct iovec iovec[26]; uint64_t coredump_size; @@ -633,7 +638,7 @@ int main(int argc, char* argv[]) { if (arg_storage != COREDUMP_STORAGE_NONE) arg_storage = COREDUMP_STORAGE_EXTERNAL; - r = save_external_coredump(info, uid, &filename, &coredump_fd, &coredump_size); + r = save_external_coredump(info, uid, &filename, &coredump_node_fd, &coredump_fd, &coredump_size); if (r < 0) goto finish; @@ -795,7 +800,7 @@ int main(int argc, char* argv[]) { coredump_vacuum(-1, arg_keep_free, arg_max_use); /* Always stream the coredump to disk, if that's possible */ - r = save_external_coredump(info, uid, &filename, &coredump_fd, &coredump_size); + r = save_external_coredump(info, uid, &filename, &coredump_node_fd, &coredump_fd, &coredump_size); if (r < 0) /* skip whole core dumping part */ goto log; @@ -815,7 +820,7 @@ int main(int argc, char* argv[]) { } /* Vacuum again, but exclude the coredump we just created */ - coredump_vacuum(coredump_fd, arg_keep_free, arg_max_use); + coredump_vacuum(coredump_node_fd >= 0 ? coredump_node_fd : coredump_fd, arg_keep_free, arg_max_use); /* Now, let's drop privileges to become the user who owns the * segfaulted process and allocate the coredump memory under diff --git a/src/journal/journal-file.c b/src/journal/journal-file.c index 9e362bacae..929ad0aa7c 100644 --- a/src/journal/journal-file.c +++ b/src/journal/journal-file.c @@ -39,6 +39,7 @@ #include "lookup3.h" #include "parse-util.h" #include "random-util.h" +#include "sd-event.h" #include "string-util.h" #include "xattr-util.h" @@ -149,6 +150,17 @@ JournalFile* journal_file_close(JournalFile *f) { journal_file_append_tag(f); #endif + if (f->post_change_timer) { + int enabled; + + if (sd_event_source_get_enabled(f->post_change_timer, &enabled) >= 0) + if (enabled == SD_EVENT_ONESHOT) + journal_file_post_change(f); + + sd_event_source_set_enabled(f->post_change_timer, SD_EVENT_OFF); + sd_event_source_unref(f->post_change_timer); + } + journal_file_set_offline(f); if (f->mmap && f->fd >= 0) @@ -1400,6 +1412,77 @@ void journal_file_post_change(JournalFile *f) { log_error_errno(errno, "Failed to truncate file to its own size: %m"); } +static int post_change_thunk(sd_event_source *timer, uint64_t usec, void *userdata) { + assert(userdata); + + journal_file_post_change(userdata); + + return 1; +} + +static void schedule_post_change(JournalFile *f) { + sd_event_source *timer; + int enabled, r; + uint64_t now; + + assert(f); + assert(f->post_change_timer); + + timer = f->post_change_timer; + + r = sd_event_source_get_enabled(timer, &enabled); + if (r < 0) { + log_error_errno(-r, "Failed to get ftruncate timer state: %m"); + return; + } + + if (enabled == SD_EVENT_ONESHOT) + return; + + r = sd_event_now(sd_event_source_get_event(timer), CLOCK_MONOTONIC, &now); + if (r < 0) { + log_error_errno(-r, "Failed to get clock's now for scheduling ftruncate: %m"); + return; + } + + r = sd_event_source_set_time(timer, now+f->post_change_timer_period); + if (r < 0) { + log_error_errno(-r, "Failed to set time for scheduling ftruncate: %m"); + return; + } + + r = sd_event_source_set_enabled(timer, SD_EVENT_ONESHOT); + if (r < 0) { + log_error_errno(-r, "Failed to enable scheduled ftruncate: %m"); + return; + } +} + +/* Enable coalesced change posting in a timer on the provided sd_event instance */ +int journal_file_enable_post_change_timer(JournalFile *f, sd_event *e, usec_t t) { + _cleanup_(sd_event_source_unrefp) sd_event_source *timer = NULL; + int r; + + assert(f); + assert_return(!f->post_change_timer, -EINVAL); + assert(e); + assert(t); + + r = sd_event_add_time(e, &timer, CLOCK_MONOTONIC, 0, 0, post_change_thunk, f); + if (r < 0) + return r; + + r = sd_event_source_set_enabled(timer, SD_EVENT_OFF); + if (r < 0) + return r; + + f->post_change_timer = timer; + timer = NULL; + f->post_change_timer_period = t; + + return r; +} + static int entry_item_cmp(const void *_a, const void *_b) { const EntryItem *a = _a, *b = _b; @@ -1465,7 +1548,10 @@ int journal_file_append_entry(JournalFile *f, const dual_timestamp *ts, const st if (mmap_cache_got_sigbus(f->mmap, f->fd)) r = -EIO; - journal_file_post_change(f); + if (f->post_change_timer) + schedule_post_change(f); + else + journal_file_post_change(f); return r; } @@ -2767,6 +2853,14 @@ int journal_file_open( goto fail; } + if (template && template->post_change_timer) { + sd_event *e = sd_event_source_get_event(template->post_change_timer); + + r = journal_file_enable_post_change_timer(f, e, template->post_change_timer_period); + if (r < 0) + goto fail; + } + *ret = f; return 0; diff --git a/src/journal/journal-file.h b/src/journal/journal-file.h index 46c1f3278e..7970ebe738 100644 --- a/src/journal/journal-file.h +++ b/src/journal/journal-file.h @@ -33,6 +33,7 @@ #include "journal-def.h" #include "macro.h" #include "mmap-cache.h" +#include "sd-event.h" #include "sparse-endian.h" typedef struct JournalMetrics { @@ -101,6 +102,9 @@ typedef struct JournalFile { JournalMetrics metrics; MMapCache *mmap; + sd_event_source *post_change_timer; + usec_t post_change_timer_period; + OrderedHashmap *chain_cache; #if defined(HAVE_XZ) || defined(HAVE_LZ4) @@ -224,6 +228,7 @@ void journal_file_print_header(JournalFile *f); int journal_file_rotate(JournalFile **f, bool compress, bool seal); void journal_file_post_change(JournalFile *f); +int journal_file_enable_post_change_timer(JournalFile *f, sd_event *e, usec_t t); void journal_reset_metrics(JournalMetrics *m); void journal_default_metrics(JournalMetrics *m, int fd); diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index f55e46382d..8c1b064e86 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -84,6 +84,9 @@ #define NOTIFY_SNDBUF_SIZE (8*1024*1024) +/* The period to insert between posting changes for coalescing */ +#define POST_CHANGE_TIMER_INTERVAL_USEC (250*USEC_PER_MSEC) + static int determine_space_for( Server *s, JournalMetrics *metrics, @@ -234,6 +237,38 @@ static void server_add_acls(JournalFile *f, uid_t uid) { #endif } +static int open_journal( + Server *s, + bool reliably, + const char *fname, + int flags, + bool seal, + JournalMetrics *metrics, + JournalFile *template, + JournalFile **ret) { + int r; + + assert(s); + assert(fname); + assert(ret); + + if (reliably) + r = journal_file_open_reliably(fname, flags, 0640, s->compress, seal, metrics, s->mmap, template, ret); + else + r = journal_file_open(fname, flags, 0640, s->compress, seal, metrics, s->mmap, template, ret); + + if (r < 0) + return r; + + r = journal_file_enable_post_change_timer(*ret, s->event, POST_CHANGE_TIMER_INTERVAL_USEC); + if (r < 0) { + *ret = journal_file_close(*ret); + return r; + } + + return r; +} + static JournalFile* find_journal(Server *s, uid_t uid) { _cleanup_free_ char *p = NULL; int r; @@ -272,7 +307,7 @@ static JournalFile* find_journal(Server *s, uid_t uid) { journal_file_close(f); } - r = journal_file_open_reliably(p, O_RDWR|O_CREAT, 0640, s->compress, s->seal, &s->system_metrics, s->mmap, NULL, &f); + r = open_journal(s, true, p, O_RDWR|O_CREAT, s->seal, &s->system_metrics, NULL, &f); if (r < 0) return s->system_journal; @@ -962,7 +997,7 @@ static int system_journal_open(Server *s, bool flush_requested) { (void) mkdir(fn, 0755); fn = strjoina(fn, "/system.journal"); - r = journal_file_open_reliably(fn, O_RDWR|O_CREAT, 0640, s->compress, s->seal, &s->system_metrics, s->mmap, NULL, &s->system_journal); + r = open_journal(s, true, fn, O_RDWR|O_CREAT, s->seal, &s->system_metrics, NULL, &s->system_journal); if (r >= 0) { server_add_acls(s->system_journal, 0); (void) determine_space_for(s, &s->system_metrics, "/var/log/journal/", "System journal", true, true, NULL, NULL); @@ -985,7 +1020,7 @@ static int system_journal_open(Server *s, bool flush_requested) { * if it already exists, so that we can flush * it into the system journal */ - r = journal_file_open(fn, O_RDWR, 0640, s->compress, false, &s->runtime_metrics, s->mmap, NULL, &s->runtime_journal); + r = open_journal(s, false, fn, O_RDWR, false, &s->runtime_metrics, NULL, &s->runtime_journal); if (r < 0) { if (r != -ENOENT) log_warning_errno(r, "Failed to open runtime journal: %m"); @@ -1002,7 +1037,7 @@ static int system_journal_open(Server *s, bool flush_requested) { (void) mkdir("/run/log/journal", 0755); (void) mkdir_parents(fn, 0750); - r = journal_file_open_reliably(fn, O_RDWR|O_CREAT, 0640, s->compress, false, &s->runtime_metrics, s->mmap, NULL, &s->runtime_journal); + r = open_journal(s, true, fn, O_RDWR|O_CREAT, false, &s->runtime_metrics, NULL, &s->runtime_journal); if (r < 0) return log_error_errno(r, "Failed to open runtime journal: %m"); } diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index 9ddc9b5aaf..e344b3b77b 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ b/src/libsystemd/sd-bus/bus-common-errors.c @@ -80,6 +80,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_RR_TYPE_UNSUPPORTED, EOPNOTSUPP), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_LINK, ENXIO), SD_BUS_ERROR_MAP(BUS_ERROR_LINK_BUSY, EBUSY), + SD_BUS_ERROR_MAP(BUS_ERROR_NETWORK_DOWN, ENETDOWN), 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 e93b6ac448..130779e8e3 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -79,6 +79,7 @@ #define BUS_ERROR_RR_TYPE_UNSUPPORTED "org.freedesktop.resolve1.ResourceRecordTypeUnsupported" #define BUS_ERROR_NO_SUCH_LINK "org.freedesktop.resolve1.NoSuchLink" #define BUS_ERROR_LINK_BUSY "org.freedesktop.resolve1.LinkBusy" +#define BUS_ERROR_NETWORK_DOWN "org.freedesktop.resolve1.NetworkDown" #define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError." #define BUS_ERROR_NO_SUCH_TRANSFER "org.freedesktop.import1.NoSuchTransfer" diff --git a/src/login/logind.c b/src/login/logind.c index 79ea5ddfcb..9723e2f4e0 100644 --- a/src/login/logind.c +++ b/src/login/logind.c @@ -70,7 +70,7 @@ static Manager *manager_new(void) { m->idle_action_not_before_usec = now(CLOCK_MONOTONIC); m->runtime_dir_size = PAGE_ALIGN((size_t) (physical_memory() / 10)); /* 10% */ - m->user_tasks_max = UINT64_C(4096); + m->user_tasks_max = UINT64_C(12288); m->devices = hashmap_new(&string_hash_ops); m->seats = hashmap_new(&string_hash_ops); diff --git a/src/login/logind.conf b/src/login/logind.conf index 81f6695434..6095e482ac 100644 --- a/src/login/logind.conf +++ b/src/login/logind.conf @@ -32,4 +32,4 @@ #IdleActionSec=30min #RuntimeDirectorySize=10% #RemoveIPC=yes -#UserTasksMax=4096 +#UserTasksMax=12288 diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index e448dd2035..28134f61bf 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -1325,7 +1325,7 @@ int manager_start_scope( if (r < 0) return r; - r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", 8192); + r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", UINT64_C(16384)); if (r < 0) return bus_log_create_error(r); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index d619206dd6..b183e3aecc 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -1027,7 +1027,7 @@ static int setup_timezone(const char *dest) { } check = strjoina("/usr/share/zoneinfo/", z); - check = prefix_root(dest, check); + check = prefix_roota(dest, check); if (laccess(check, F_OK) < 0) { log_warning("Timezone %s does not exist in container, not updating container timezone.", z); return 0; diff --git a/src/resolve-host/Makefile b/src/resolve-host/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/resolve-host/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile
\ No newline at end of file diff --git a/src/resolve/dns-type.c b/src/resolve/dns-type.c index 058d14009a..56720646ca 100644 --- a/src/resolve/dns-type.c +++ b/src/resolve/dns-type.c @@ -19,6 +19,8 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <sys/socket.h> + #include "dns-type.h" #include "string-util.h" @@ -183,6 +185,23 @@ bool dns_type_is_obsolete(uint16_t type) { DNS_TYPE_NULL); } +int dns_type_to_af(uint16_t t) { + switch (t) { + + case DNS_TYPE_A: + return AF_INET; + + case DNS_TYPE_AAAA: + return AF_INET6; + + case DNS_TYPE_ANY: + return AF_UNSPEC; + + default: + return -EINVAL; + } +} + const char *dns_class_to_string(uint16_t class) { switch (class) { diff --git a/src/resolve/dns-type.h b/src/resolve/dns-type.h index 78ff71b06e..60ff160383 100644 --- a/src/resolve/dns-type.h +++ b/src/resolve/dns-type.h @@ -133,6 +133,7 @@ bool dns_type_is_dnssec(uint16_t type); bool dns_type_is_obsolete(uint16_t type); bool dns_type_may_wildcard(uint16_t type); bool dns_type_apex_only(uint16_t type); +int dns_type_to_af(uint16_t t); bool dns_class_is_pseudo(uint16_t class); bool dns_class_is_valid_rr(uint16_t class); diff --git a/src/resolve-host/resolve-host.c b/src/resolve/resolve-tool.c index 54a060ea5a..fdaeb8d926 100644 --- a/src/resolve-host/resolve-host.c +++ b/src/resolve/resolve-tool.c @@ -867,11 +867,11 @@ static int show_statistics(sd_bus *bus) { 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", + printf("\n%sDNSSEC Verdicts%s\n" + " Secure: %" PRIu64 "\n" + " Insecure: %" PRIu64 "\n" + " Bogus: %" PRIu64 "\n" + " Indeterminate: %" PRIu64 "\n", ansi_highlight(), ansi_normal(), n_dnssec_secure, @@ -929,7 +929,7 @@ static void help_dns_classes(void) { static void help(void) { printf("%s [OPTIONS...] NAME...\n" "%s [OPTIONS...] --service [[NAME] TYPE] DOMAIN\n\n" - "Resolve domain names, IPv4 or IPv6 addresses, resource records, and services.\n\n" + "Resolve domain names, IPv4 and IPv6 addresses, DNS resource records, and services.\n\n" " -h --help Show this help\n" " --version Show package version\n" " -4 Resolve IPv4 addresses\n" @@ -943,7 +943,7 @@ static void help(void) { " --service-txt=BOOL Do [not] resolve TXT records for services\n" " --cname=BOOL Do [not] follow CNAME redirects\n" " --search=BOOL Do [not] use search domains\n" - " --legend=BOOL Do [not] print column headers\n" + " --legend=BOOL Do [not] print column headers and meta information\n" " --statistics Show resolver statistics\n" " --reset-statistics Reset resolver statistics\n" , program_invocation_short_name, program_invocation_short_name); @@ -1175,7 +1175,7 @@ int main(int argc, char **argv) { case MODE_RESOLVE_HOST: if (optind >= argc) { - log_error("No arguments passed"); + log_error("No arguments passed."); r = -EINVAL; goto finish; } @@ -1203,7 +1203,7 @@ int main(int argc, char **argv) { case MODE_RESOLVE_RECORD: if (optind >= argc) { - log_error("No arguments passed"); + log_error("No arguments passed."); r = -EINVAL; goto finish; } @@ -1232,7 +1232,7 @@ int main(int argc, char **argv) { else if (argc == optind + 3) r = resolve_service(bus, argv[optind], argv[optind+1], argv[optind+2]); else { - log_error("Too many arguments"); + log_error("Too many arguments."); r = -EINVAL; goto finish; } diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 9110ea52a6..d8e8638327 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -43,8 +43,8 @@ static int reply_query_state(DnsQuery *q) { case DNS_TRANSACTION_INVALID_REPLY: return sd_bus_reply_method_errorf(q->request, BUS_ERROR_INVALID_REPLY, "Received invalid reply"); - case DNS_TRANSACTION_RESOURCES: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_RESOURCES, "Not enough resources"); + case DNS_TRANSACTION_ERRNO: + return sd_bus_reply_method_errnof(q->request, q->answer_errno, "Lookup failed due to system error: %m"); case DNS_TRANSACTION_ABORTED: return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "Query aborted"); @@ -59,6 +59,14 @@ static int reply_query_state(DnsQuery *q) { 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_NETWORK_DOWN: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NETWORK_DOWN, "Network is down"); + + case DNS_TRANSACTION_NOT_FOUND: + /* We return this as NXDOMAIN. This is only generated when a host doesn't implement LLMNR/TCP, and we + * thus quickly know that we cannot resolve an in-addr.arpa or ip6.arpa address. */ + return sd_bus_reply_method_errorf(q->request, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q)); + case DNS_TRANSACTION_RCODE_FAILURE: { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -66,7 +74,7 @@ static int reply_query_state(DnsQuery *q) { sd_bus_error_setf(&error, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q)); else { const char *rc, *n; - char p[3]; /* the rcode is 4 bits long */ + char p[DECIMAL_STR_MAX(q->answer_rcode)]; rc = dns_rcode_to_string(q->answer_rcode); if (!rc) { @@ -133,8 +141,9 @@ static int append_address(sd_bus_message *reply, DnsResourceRecord *rr, int ifin static void bus_method_resolve_hostname_complete(DnsQuery *q) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + DnsResourceRecord *rr; unsigned added = 0; - int r; + int ifindex, r; assert(q); @@ -161,30 +170,25 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { if (r < 0) goto finish; - if (q->answer) { - DnsResourceRecord *rr; - int ifindex; - - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - DnsQuestion *question; + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + DnsQuestion *question; - question = dns_query_question_for_protocol(q, q->answer_protocol); + question = dns_query_question_for_protocol(q, q->answer_protocol); - r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); - if (r < 0) - goto finish; - if (r == 0) - continue; + r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); + if (r < 0) + goto finish; + if (r == 0) + continue; - r = append_address(reply, rr, ifindex); - if (r < 0) - goto finish; + r = append_address(reply, rr, ifindex); + if (r < 0) + goto finish; - if (!canonical) - canonical = dns_resource_record_ref(rr); + if (!canonical) + canonical = dns_resource_record_ref(rr); - added ++; - } + added ++; } if (added <= 0) { @@ -1293,10 +1297,10 @@ static int bus_property_get_dnssec_statistics( 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); + (uint64_t) m->n_dnssec_verdict[DNSSEC_SECURE], + (uint64_t) m->n_dnssec_verdict[DNSSEC_INSECURE], + (uint64_t) m->n_dnssec_verdict[DNSSEC_BOGUS], + (uint64_t) m->n_dnssec_verdict[DNSSEC_INDETERMINATE]); } static int bus_property_get_dnssec_supported( @@ -1327,7 +1331,7 @@ static int bus_method_reset_statistics(sd_bus_message *message, void *userdata, 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; + zero(m->n_dnssec_verdict); return sd_bus_reply_method_return(message, NULL); } diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index fdb34d11df..9267b67f79 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -579,6 +579,28 @@ static void dns_cache_remove_previous( } } +static bool rr_eligible(DnsResourceRecord *rr) { + assert(rr); + + /* When we see an NSEC/NSEC3 RR, we'll only cache it if it is from the lower zone, not the upper zone, since + * that's where the interesting bits are (with exception of DS RRs). Of course, this way we cannot derive DS + * existence from any cached NSEC/NSEC3, but that should be fine. */ + + switch (rr->key->type) { + + case DNS_TYPE_NSEC: + return !bitmap_isset(rr->nsec.types, DNS_TYPE_NS) || + bitmap_isset(rr->nsec.types, DNS_TYPE_SOA); + + case DNS_TYPE_NSEC3: + return !bitmap_isset(rr->nsec3.types, DNS_TYPE_NS) || + bitmap_isset(rr->nsec3.types, DNS_TYPE_SOA); + + default: + return true; + } +} + int dns_cache_put( DnsCache *c, DnsResourceKey *key, @@ -635,6 +657,12 @@ int dns_cache_put( if ((flags & DNS_ANSWER_CACHEABLE) == 0) continue; + r = rr_eligible(rr); + if (r < 0) + return r; + if (r == 0) + continue; + r = dns_cache_put_positive( c, rr, @@ -835,7 +863,10 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r have_non_authenticated = true; } - if (nsec && key->type != DNS_TYPE_NSEC) { + if (nsec && !IN_SET(key->type, DNS_TYPE_NSEC, DNS_TYPE_DS)) { + /* Note that we won't derive information for DS RRs from an NSEC, because we only cache NSEC RRs from + * the lower-zone of a zone cut, but the DS RRs are on the upper zone. */ + if (log_get_max_level() >= LOG_DEBUG) { r = dns_resource_key_to_string(key, &key_str); if (r < 0) diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 1f48f588ce..8e3c78e7bf 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -35,9 +35,6 @@ * * TODO: * - * - bus calls to override DNSEC setting per interface - * - log all DNSSEC downgrades - * - log all RRs that failed validation * - enable by default * - Allow clients to request DNSSEC even if DNSSEC is off * - make sure when getting an NXDOMAIN response through CNAME, we still process the first CNAMEs in the packet @@ -1270,11 +1267,12 @@ static int nsec3_is_good(DnsResourceRecord *rr, DnsResourceRecord *nsec3) { if (rr->nsec3.iterations > NSEC3_ITERATIONS_MAX) return 0; - /* Ignore NSEC3 RRs generated from wildcards */ - if (rr->n_skip_labels_source != 0) + /* Ignore NSEC3 RRs generated from wildcards. If these NSEC3 RRs weren't correctly signed we can't make this + * check (since rr->n_skip_labels_source is -1), but that's OK, as we won't trust them anyway in that case. */ + if (rr->n_skip_labels_source != 0 && rr->n_skip_labels_source != (unsigned) -1) return 0; /* Ignore NSEC3 RRs that are located anywhere else than one label below the zone */ - if (rr->n_skip_labels_signer != 1) + if (rr->n_skip_labels_signer != 1 && rr->n_skip_labels_signer != (unsigned) -1) return 0; if (!nsec3) @@ -1458,19 +1456,20 @@ found_zone: 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) { + /* We have an exact match! If we area looking for a DS RR, then we must insist that we got the NSEC3 RR + * from the parent. Otherwise the one from the child. Do so, by checking whether SOA and NS are + * appropriately set. */ + + if (key->type == DNS_TYPE_DS) { + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) + return -EBADMSG; + } else { + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_NS) && + !bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) + return -EBADMSG; + } + /* 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; @@ -1487,6 +1486,18 @@ found_closest_encloser: return 0; } + /* 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; + /* Prove that there is no next closer and whether or not there is a wildcard domain. */ wildcard = strjoina("*.", p); @@ -2129,3 +2140,11 @@ static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = { [DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server", }; DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult); + +static const char* const dnssec_verdict_table[_DNSSEC_VERDICT_MAX] = { + [DNSSEC_SECURE] = "secure", + [DNSSEC_INSECURE] = "insecure", + [DNSSEC_BOGUS] = "bogus", + [DNSSEC_INDETERMINATE] = "indeterminate", +}; +DEFINE_STRING_TABLE_LOOKUP(dnssec_verdict, DnssecVerdict); diff --git a/src/resolve/resolved-dns-dnssec.h b/src/resolve/resolved-dns-dnssec.h index 955017e8cb..c99861b8e5 100644 --- a/src/resolve/resolved-dns-dnssec.h +++ b/src/resolve/resolved-dns-dnssec.h @@ -21,8 +21,8 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -typedef enum DnssecMode DnssecMode; typedef enum DnssecResult DnssecResult; +typedef enum DnssecVerdict DnssecVerdict; #include "dns-domain.h" #include "resolved-dns-answer.h" @@ -50,6 +50,16 @@ enum DnssecResult { _DNSSEC_RESULT_INVALID = -1 }; +enum DnssecVerdict { + DNSSEC_SECURE, + DNSSEC_INSECURE, + DNSSEC_BOGUS, + DNSSEC_INDETERMINATE, + + _DNSSEC_VERDICT_MAX, + _DNSSEC_VERDICT_INVALID = -1 +}; + #define DNSSEC_CANONICAL_HOSTNAME_MAX (DNS_HOSTNAME_MAX + 2) /* The longest digest we'll ever generate, of all digest algorithms we support */ @@ -90,3 +100,6 @@ int dnssec_test_positive_wildcard(DnsAnswer *a, const char *name, const char *so const char* dnssec_result_to_string(DnssecResult m) _const_; DnssecResult dnssec_result_from_string(const char *s) _pure_; + +const char* dnssec_verdict_to_string(DnssecVerdict m) _const_; +DnssecVerdict dnssec_verdict_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 9a5223ef01..032e719595 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -2094,7 +2094,7 @@ int dns_packet_extract(DnsPacket *p) { n = DNS_PACKET_RRCOUNT(p); if (n > 0) { - DnsResourceRecord *previous = NULL; + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *previous = NULL; bool bad_opt = false; answer = dns_answer_new(n); @@ -2150,12 +2150,12 @@ int dns_packet_extract(DnsPacket *p) { } if (has_rfc6975) { - /* OPT RR contains RFC6975 algorithm data, then this is indication that the - * server just copied the OPT it got from us (which contained that data) back - * into the reply. If so, then it doesn't properly support EDNS, as RFC6975 - * makes it very clear that the algorithm data should only be contained in - * questions, never in replies. Crappy Belkin copy the OPT data for example, - * hence let's detect this so that we downgrade early. */ + /* If the OPT RR contains RFC6975 algorithm data, then this is indication that + * the server just copied the OPT it got from us (which contained that data) + * back into the reply. If so, then it doesn't properly support EDNS, as + * RFC6975 makes it very clear that the algorithm data should only be contained + * in questions, never in replies. Crappy Belkin routers copy the OPT data for + * example, hence let's detect this so that we downgrade early. */ log_debug("OPT RR contained RFC6975 data, ignoring."); bad_opt = true; continue; @@ -2174,6 +2174,11 @@ int dns_packet_extract(DnsPacket *p) { if (r < 0) goto finish; } + + /* Remember this RR, so that we potentically can merge it's ->key object with the next RR. Note + * that we only do this if we actually decided to keep the RR around. */ + dns_resource_record_unref(previous); + previous = dns_resource_record_ref(rr); } if (bad_opt) diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index fc5bf4020f..ef977b0948 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -24,6 +24,8 @@ #include "hostname-util.h" #include "local-addresses.h" #include "resolved-dns-query.h" +#include "resolved-dns-synthesize.h" +#include "resolved-etc-hosts.h" #include "string-util.h" /* How long to wait for the query in total */ @@ -180,7 +182,7 @@ static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) { assert(c); if (c->error_code != 0) - return DNS_TRANSACTION_RESOURCES; + return DNS_TRANSACTION_ERRNO; SET_FOREACH(t, c->transactions, i) { @@ -322,6 +324,7 @@ static void dns_query_reset_answer(DnsQuery *q) { q->answer = dns_answer_unref(q->answer); q->answer_rcode = 0; q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + q->answer_errno = 0; q->answer_authenticated = false; q->answer_protocol = _DNS_PROTOCOL_INVALID; q->answer_family = AF_UNSPEC; @@ -547,413 +550,73 @@ fail: return r; } -static int SYNTHESIZE_IFINDEX(int ifindex) { - - /* When the caller asked for resolving on a specific - * interface, we synthesize the answer for that - * interface. However, if nothing specific was claimed and we - * only return localhost RRs, we synthesize the answer for - * localhost. */ - - if (ifindex > 0) - return ifindex; - - return LOOPBACK_IFINDEX; -} - -static int SYNTHESIZE_FAMILY(uint64_t flags) { - - /* Picks an address family depending on set flags. This is - * purely for synthesized answers, where the family we return - * for the reply should match what was requested in the - * question, even though we are synthesizing the answer - * here. */ - - if (!(flags & SD_RESOLVED_DNS)) { - if (flags & SD_RESOLVED_LLMNR_IPV4) - return AF_INET; - if (flags & SD_RESOLVED_LLMNR_IPV6) - return AF_INET6; - } - - return AF_UNSPEC; -} - -static DnsProtocol SYNTHESIZE_PROTOCOL(uint64_t flags) { - - /* Similar as SYNTHESIZE_FAMILY() but does this for the - * protocol. If resolving via DNS was requested, we claim it - * was DNS. Similar, if nothing specific was - * requested. However, if only resolving via LLMNR was - * requested we return that. */ - - if (flags & SD_RESOLVED_DNS) - return DNS_PROTOCOL_DNS; - if (flags & SD_RESOLVED_LLMNR) - return DNS_PROTOCOL_LLMNR; - - return DNS_PROTOCOL_DNS; -} - -static int dns_type_to_af(uint16_t t) { - switch (t) { - - case DNS_TYPE_A: - return AF_INET; - - case DNS_TYPE_AAAA: - return AF_INET6; - - case DNS_TYPE_ANY: - return AF_UNSPEC; - - default: - return -EINVAL; - } -} - -static int synthesize_localhost_rr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) { - int r; - - assert(q); - assert(key); - assert(answer); - - r = dns_answer_reserve(answer, 2); - if (r < 0) - return r; - - if (IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_ANY)) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, DNS_RESOURCE_KEY_NAME(key)); - if (!rr) - return -ENOMEM; - - rr->a.in_addr.s_addr = htobe32(INADDR_LOOPBACK); - - r = dns_answer_add(*answer, rr, SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - if (IN_SET(key->type, DNS_TYPE_AAAA, DNS_TYPE_ANY)) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_AAAA, DNS_RESOURCE_KEY_NAME(key)); - if (!rr) - return -ENOMEM; - - rr->aaaa.in6_addr = in6addr_loopback; - - r = dns_answer_add(*answer, rr, SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - return 0; -} - -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); - if (!rr) - return -ENOMEM; - - rr->ptr.name = strdup(to); - if (!rr->ptr.name) - return -ENOMEM; - - return dns_answer_add(*answer, rr, ifindex, flags); -} - -static int synthesize_localhost_ptr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) { - int r; - - assert(q); - assert(key); - assert(answer); - - if (IN_SET(key->type, DNS_TYPE_PTR, DNS_TYPE_ANY)) { - 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; - } - - return 0; -} - -static int answer_add_addresses_rr( - DnsAnswer **answer, - const char *name, - struct local_address *addresses, - unsigned n_addresses) { - - unsigned j; - int r; - - assert(answer); - assert(name); - - r = dns_answer_reserve(answer, n_addresses); - if (r < 0) - return r; - - for (j = 0; j < n_addresses; j++) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - r = dns_resource_record_new_address(&rr, addresses[j].family, &addresses[j].address, name); - if (r < 0) - return r; - - r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - return 0; -} - -static int answer_add_addresses_ptr( - DnsAnswer **answer, - const char *name, - struct local_address *addresses, - unsigned n_addresses, - int af, const union in_addr_union *match) { - - unsigned j; - int r; - - assert(answer); - assert(name); - - for (j = 0; j < n_addresses; j++) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - if (af != AF_UNSPEC) { - - if (addresses[j].family != af) - continue; - - if (match && !in_addr_equal(af, match, &addresses[j].address)) - continue; - } - - r = dns_answer_reserve(answer, 1); - if (r < 0) - return r; - - r = dns_resource_record_new_reverse(&rr, addresses[j].family, &addresses[j].address, name); - if (r < 0) - return r; - - r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - return 0; -} - -static int synthesize_system_hostname_rr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) { - _cleanup_free_ struct local_address *addresses = NULL; - int n = 0, af; - - assert(q); - assert(key); - assert(answer); - - af = dns_type_to_af(key->type); - if (af >= 0) { - n = local_addresses(q->manager->rtnl, q->ifindex, af, &addresses); - if (n < 0) - return n; - - if (n == 0) { - struct local_address buffer[2]; - - /* If we have no local addresses then use ::1 - * and 127.0.0.2 as local ones. */ - - if (af == AF_INET || af == AF_UNSPEC) - buffer[n++] = (struct local_address) { - .family = AF_INET, - .ifindex = SYNTHESIZE_IFINDEX(q->ifindex), - .address.in.s_addr = htobe32(0x7F000002), - }; - - if (af == AF_INET6 || af == AF_UNSPEC) - buffer[n++] = (struct local_address) { - .family = AF_INET6, - .ifindex = SYNTHESIZE_IFINDEX(q->ifindex), - .address.in6 = in6addr_loopback, - }; - - return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), buffer, n); - } - } - - return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), addresses, n); -} - -static int synthesize_system_hostname_ptr(DnsQuery *q, int af, const union in_addr_union *address, DnsAnswer **answer) { - _cleanup_free_ struct local_address *addresses = NULL; - int n, r; - - assert(q); - assert(address); - assert(answer); - - if (af == AF_INET && address->in.s_addr == htobe32(0x7F000002)) { - - /* Always map the IPv4 address 127.0.0.2 to the local - * hostname, in addition to "localhost": */ - - r = dns_answer_reserve(answer, 3); - 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), 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), 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), DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - - return 0; - } - - n = local_addresses(q->manager->rtnl, q->ifindex, af, &addresses); - if (n < 0) - return n; - - r = answer_add_addresses_ptr(answer, q->manager->llmnr_hostname, addresses, n, af, address); - if (r < 0) - return r; - - return answer_add_addresses_ptr(answer, q->manager->mdns_hostname, addresses, n, af, address); -} - -static int synthesize_gateway_rr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) { - _cleanup_free_ struct local_address *addresses = NULL; - int n = 0, af; - - assert(q); - assert(key); - assert(answer); - - af = dns_type_to_af(key->type); - if (af >= 0) { - n = local_gateways(q->manager->rtnl, q->ifindex, af, &addresses); - if (n < 0) - return n; - } - - return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), addresses, n); -} - -static int synthesize_gateway_ptr(DnsQuery *q, int af, const union in_addr_union *address, DnsAnswer **answer) { - _cleanup_free_ struct local_address *addresses = NULL; - int n; - - assert(q); - assert(address); - assert(answer); - - n = local_gateways(q->manager->rtnl, q->ifindex, af, &addresses); - if (n < 0) - return n; - - return answer_add_addresses_ptr(answer, "gateway", addresses, n, af, address); -} - static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - DnsResourceKey *key; int r; assert(q); assert(state); - /* Tries to synthesize localhost RR replies where appropriate */ + /* Tries to synthesize localhost RR replies (and others) where appropriate. Note that this is done *after* the + * the normal lookup finished. The data from the network hence takes precedence over the data we + * synthesize. (But note that many scopes refuse to resolve certain domain names) */ if (!IN_SET(*state, DNS_TRANSACTION_RCODE_FAILURE, DNS_TRANSACTION_NO_SERVERS, DNS_TRANSACTION_TIMEOUT, - DNS_TRANSACTION_ATTEMPTS_MAX_REACHED)) + DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, + DNS_TRANSACTION_NETWORK_DOWN, + DNS_TRANSACTION_NOT_FOUND)) return 0; - DNS_QUESTION_FOREACH(key, q->question_utf8) { - union in_addr_union address; - const char *name; - int af; - - if (key->class != DNS_CLASS_IN && - key->class != DNS_CLASS_ANY) - continue; - - name = DNS_RESOURCE_KEY_NAME(key); + r = dns_synthesize_answer( + q->manager, + q->question_utf8, + q->ifindex, + &answer); - if (is_localhost(name)) { - - r = synthesize_localhost_rr(q, key, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize localhost RRs: %m"); - - } else if (manager_is_own_hostname(q->manager, name)) { + if (r <= 0) + return r; - r = synthesize_system_hostname_rr(q, key, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize system hostname RRs: %m"); + dns_query_reset_answer(q); - } else if (is_gateway_hostname(name)) { + q->answer = answer; + answer = NULL; + q->answer_rcode = DNS_RCODE_SUCCESS; + q->answer_protocol = dns_synthesize_protocol(q->flags); + q->answer_family = dns_synthesize_family(q->flags); + q->answer_authenticated = true; - r = synthesize_gateway_rr(q, key, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize gateway RRs: %m"); + *state = DNS_TRANSACTION_SUCCESS; - } else if ((dns_name_endswith(name, "127.in-addr.arpa") > 0 && dns_name_equal(name, "2.0.0.127.in-addr.arpa") == 0) || - dns_name_equal(name, "1.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 1; +} - r = synthesize_localhost_ptr(q, key, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize localhost PTR RRs: %m"); +static int dns_query_try_etc_hosts(DnsQuery *q) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + int r; - } else if (dns_name_address(name, &af, &address) > 0) { + assert(q); - r = synthesize_system_hostname_ptr(q, af, &address, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize system hostname PTR RR: %m"); + /* Looks in /etc/hosts for matching entries. Note that this is done *before* the normal lookup is done. The + * data from /etc/hosts hence takes precedence over the network. */ - r = synthesize_gateway_ptr(q, af, &address, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize gateway hostname PTR RR: %m"); - } - } + r = manager_etc_hosts_lookup( + q->manager, + q->question_utf8, + &answer); + if (r <= 0) + return r; - if (!answer) - return 0; + dns_query_reset_answer(q); - dns_answer_unref(q->answer); q->answer = answer; answer = NULL; - q->answer_rcode = DNS_RCODE_SUCCESS; - q->answer_protocol = SYNTHESIZE_PROTOCOL(q->flags); - q->answer_family = SYNTHESIZE_FAMILY(q->flags); - - *state = DNS_TRANSACTION_SUCCESS; + q->answer_protocol = dns_synthesize_protocol(q->flags); + q->answer_family = dns_synthesize_family(q->flags); + q->answer_authenticated = true; return 1; } @@ -969,6 +632,14 @@ int dns_query_go(DnsQuery *q) { if (q->state != DNS_TRANSACTION_NULL) return 0; + r = dns_query_try_etc_hosts(q); + if (r < 0) + return r; + if (r > 0) { + dns_query_complete(q, DNS_TRANSACTION_SUCCESS); + return 1; + } + LIST_FOREACH(scopes, s, q->manager->dns_scopes) { DnsScopeMatch match; const char *name; @@ -1000,7 +671,10 @@ int dns_query_go(DnsQuery *q) { if (found == DNS_SCOPE_NO) { DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; - dns_query_synthesize_reply(q, &state); + r = dns_query_synthesize_reply(q, &state); + if (r < 0) + return r; + dns_query_complete(q, state); return 1; } @@ -1029,10 +703,7 @@ int dns_query_go(DnsQuery *q) { goto fail; } - q->answer = dns_answer_unref(q->answer); - q->answer_rcode = 0; - q->answer_family = AF_UNSPEC; - q->answer_protocol = _DNS_PROTOCOL_INVALID; + dns_query_reset_answer(q); r = sd_event_add_time( q->manager->event, @@ -1078,11 +749,23 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { assert(q); if (!c) { - dns_query_synthesize_reply(q, &state); + r = dns_query_synthesize_reply(q, &state); + if (r < 0) + goto fail; + dns_query_complete(q, state); return; } + if (c->error_code != 0) { + /* If the candidate had an error condition of its own, start with that. */ + state = DNS_TRANSACTION_ERRNO; + q->answer = dns_answer_unref(q->answer); + q->answer_rcode = 0; + q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + q->answer_errno = c->error_code; + } + SET_FOREACH(t, c->transactions, i) { switch (t->state) { @@ -1090,12 +773,11 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { case DNS_TRANSACTION_SUCCESS: { /* We found a successfuly reply, merge it into the answer */ r = dns_answer_extend(&q->answer, t->answer); - if (r < 0) { - dns_query_complete(q, DNS_TRANSACTION_RESOURCES); - return; - } + if (r < 0) + goto fail; q->answer_rcode = t->answer_rcode; + q->answer_errno = 0; if (t->answer_authenticated) { has_authenticated = true; @@ -1126,6 +808,7 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { q->answer = dns_answer_unref(q->answer); q->answer_rcode = t->answer_rcode; q->answer_dnssec_result = t->answer_dnssec_result; + q->answer_errno = t->answer_errno; state = t->state; break; @@ -1143,8 +826,16 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { dns_search_domain_unref(q->answer_search_domain); q->answer_search_domain = dns_search_domain_ref(c->search_domain); - dns_query_synthesize_reply(q, &state); + r = dns_query_synthesize_reply(q, &state); + if (r < 0) + goto fail; + dns_query_complete(q, state); + return; + +fail: + q->answer_errno = -r; + dns_query_complete(q, DNS_TRANSACTION_ERRNO); } void dns_query_ready(DnsQuery *q) { diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h index 9f618d6f6b..c7e4ce9a00 100644 --- a/src/resolve/resolved-dns-query.h +++ b/src/resolve/resolved-dns-query.h @@ -83,6 +83,7 @@ struct DnsQuery { DnsProtocol answer_protocol; int answer_family; DnsSearchDomain *answer_search_domain; + int answer_errno; /* if state is DNS_TRANSACTION_ERRNO */ /* Bus client information */ sd_bus_message *request; diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 8a52d66fad..ac4887abea 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -1015,3 +1015,22 @@ bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name) { return dns_name_is_single_label(name); } + +bool dns_scope_network_good(DnsScope *s) { + Iterator i; + Link *l; + + /* Checks whether the network is in good state for lookups on this scope. For mDNS/LLMNR/Classic DNS scopes + * bound to links this is easy, as they don't even exist if the link isn't in a suitable state. For the global + * DNS scope we check whether there are any links that are up and have an address. */ + + if (s->link) + return true; + + HASHMAP_FOREACH(l, s->manager->links, i) { + if (link_relevant(l, AF_UNSPEC, false)) + return true; + } + + return false; +} diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index a0676bd30e..f9b63d56d9 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -107,3 +107,5 @@ void dns_scope_dump(DnsScope *s, FILE *f); DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s); bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name); + +bool dns_scope_network_good(DnsScope *s); diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index 5a86661807..e1d2025863 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -19,6 +19,8 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <sd-messages.h> + #include "alloc-util.h" #include "resolved-dns-server.h" #include "resolved-resolv-conf.h" @@ -547,6 +549,22 @@ bool dns_server_dnssec_supported(DnsServer *server) { return true; } +void dns_server_warn_downgrade(DnsServer *server) { + assert(server); + + if (server->warned_downgrade) + return; + + log_struct(LOG_NOTICE, + LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_DOWNGRADE), + LOG_MESSAGE("Server %s does not support DNSSEC, downgrading to non-DNSSEC mode.", dns_server_string(server)), + "DNS_SERVER=%s", dns_server_string(server), + "DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(server->possible_feature_level), + NULL); + + server->warned_downgrade = true; +} + static void dns_server_hash_func(const void *p, struct siphash *state) { const DnsServer *s = p; diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h index 02bd3463a7..2a3c921678 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -82,6 +82,9 @@ struct DnsServer { usec_t verified_usec; usec_t features_grace_period_usec; + /* Whether we already warned about downgrading to non-DNSSEC mode for this server */ + bool warned_downgrade:1; + /* Used when GC'ing old DNS servers when configuration changes. */ bool marked:1; @@ -119,6 +122,8 @@ const char *dns_server_string(DnsServer *server); bool dns_server_dnssec_supported(DnsServer *server); +void dns_server_warn_downgrade(DnsServer *server); + DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr); void dns_server_unlink_all(DnsServer *first); diff --git a/src/resolve/resolved-dns-synthesize.c b/src/resolve/resolved-dns-synthesize.c new file mode 100644 index 0000000000..f4a43dee8c --- /dev/null +++ b/src/resolve/resolved-dns-synthesize.c @@ -0,0 +1,413 @@ +/*** + 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 "alloc-util.h" +#include "hostname-util.h" +#include "local-addresses.h" +#include "resolved-dns-synthesize.h" + +int dns_synthesize_ifindex(int ifindex) { + + /* When the caller asked for resolving on a specific + * interface, we synthesize the answer for that + * interface. However, if nothing specific was claimed and we + * only return localhost RRs, we synthesize the answer for + * localhost. */ + + if (ifindex > 0) + return ifindex; + + return LOOPBACK_IFINDEX; +} + +int dns_synthesize_family(uint64_t flags) { + + /* Picks an address family depending on set flags. This is + * purely for synthesized answers, where the family we return + * for the reply should match what was requested in the + * question, even though we are synthesizing the answer + * here. */ + + if (!(flags & SD_RESOLVED_DNS)) { + if (flags & (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_MDNS_IPV4)) + return AF_INET; + if (flags & (SD_RESOLVED_LLMNR_IPV6|SD_RESOLVED_MDNS_IPV6)) + return AF_INET6; + } + + return AF_UNSPEC; +} + +DnsProtocol dns_synthesize_protocol(uint64_t flags) { + + /* Similar as dns_synthesize_family() but does this for the + * protocol. If resolving via DNS was requested, we claim it + * was DNS. Similar, if nothing specific was + * requested. However, if only resolving via LLMNR was + * requested we return that. */ + + if (flags & SD_RESOLVED_DNS) + return DNS_PROTOCOL_DNS; + if (flags & SD_RESOLVED_LLMNR) + return DNS_PROTOCOL_LLMNR; + if (flags & SD_RESOLVED_MDNS) + return DNS_PROTOCOL_MDNS; + + return DNS_PROTOCOL_DNS; +} + +static int synthesize_localhost_rr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { + int r; + + assert(m); + assert(key); + assert(answer); + + r = dns_answer_reserve(answer, 2); + if (r < 0) + return r; + + if (IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_ANY)) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, DNS_RESOURCE_KEY_NAME(key)); + if (!rr) + return -ENOMEM; + + rr->a.in_addr.s_addr = htobe32(INADDR_LOOPBACK); + + r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + if (IN_SET(key->type, DNS_TYPE_AAAA, DNS_TYPE_ANY)) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_AAAA, DNS_RESOURCE_KEY_NAME(key)); + if (!rr) + return -ENOMEM; + + rr->aaaa.in6_addr = in6addr_loopback; + + r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 0; +} + +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); + if (!rr) + return -ENOMEM; + + rr->ptr.name = strdup(to); + if (!rr->ptr.name) + return -ENOMEM; + + return dns_answer_add(*answer, rr, ifindex, flags); +} + +static int synthesize_localhost_ptr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { + int r; + + assert(m); + assert(key); + assert(answer); + + if (IN_SET(key->type, DNS_TYPE_PTR, DNS_TYPE_ANY)) { + r = dns_answer_reserve(answer, 1); + if (r < 0) + return r; + + r = answer_add_ptr(answer, DNS_RESOURCE_KEY_NAME(key), "localhost", dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 0; +} + +static int answer_add_addresses_rr( + DnsAnswer **answer, + const char *name, + struct local_address *addresses, + unsigned n_addresses) { + + unsigned j; + int r; + + assert(answer); + assert(name); + + r = dns_answer_reserve(answer, n_addresses); + if (r < 0) + return r; + + for (j = 0; j < n_addresses; j++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + r = dns_resource_record_new_address(&rr, addresses[j].family, &addresses[j].address, name); + if (r < 0) + return r; + + r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 0; +} + +static int answer_add_addresses_ptr( + DnsAnswer **answer, + const char *name, + struct local_address *addresses, + unsigned n_addresses, + int af, const union in_addr_union *match) { + + unsigned j; + int r; + + assert(answer); + assert(name); + + for (j = 0; j < n_addresses; j++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + if (af != AF_UNSPEC) { + + if (addresses[j].family != af) + continue; + + if (match && !in_addr_equal(af, match, &addresses[j].address)) + continue; + } + + r = dns_answer_reserve(answer, 1); + if (r < 0) + return r; + + r = dns_resource_record_new_reverse(&rr, addresses[j].family, &addresses[j].address, name); + if (r < 0) + return r; + + r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 0; +} + +static int synthesize_system_hostname_rr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { + _cleanup_free_ struct local_address *addresses = NULL; + int n = 0, af; + + assert(m); + assert(key); + assert(answer); + + af = dns_type_to_af(key->type); + if (af >= 0) { + n = local_addresses(m->rtnl, ifindex, af, &addresses); + if (n < 0) + return n; + + if (n == 0) { + struct local_address buffer[2]; + + /* If we have no local addresses then use ::1 + * and 127.0.0.2 as local ones. */ + + if (af == AF_INET || af == AF_UNSPEC) + buffer[n++] = (struct local_address) { + .family = AF_INET, + .ifindex = dns_synthesize_ifindex(ifindex), + .address.in.s_addr = htobe32(0x7F000002), + }; + + if (af == AF_INET6 || af == AF_UNSPEC) + buffer[n++] = (struct local_address) { + .family = AF_INET6, + .ifindex = dns_synthesize_ifindex(ifindex), + .address.in6 = in6addr_loopback, + }; + + return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), buffer, n); + } + } + + return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), addresses, n); +} + +static int synthesize_system_hostname_ptr(Manager *m, int af, const union in_addr_union *address, int ifindex, DnsAnswer **answer) { + _cleanup_free_ struct local_address *addresses = NULL; + int n, r; + + assert(m); + assert(address); + assert(answer); + + if (af == AF_INET && address->in.s_addr == htobe32(0x7F000002)) { + + /* Always map the IPv4 address 127.0.0.2 to the local + * hostname, in addition to "localhost": */ + + r = dns_answer_reserve(answer, 3); + if (r < 0) + return r; + + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", m->llmnr_hostname, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", m->mdns_hostname, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", "localhost", dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + return 0; + } + + n = local_addresses(m->rtnl, ifindex, af, &addresses); + if (n < 0) + return n; + + r = answer_add_addresses_ptr(answer, m->llmnr_hostname, addresses, n, af, address); + if (r < 0) + return r; + + return answer_add_addresses_ptr(answer, m->mdns_hostname, addresses, n, af, address); +} + +static int synthesize_gateway_rr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { + _cleanup_free_ struct local_address *addresses = NULL; + int n = 0, af; + + assert(m); + assert(key); + assert(answer); + + af = dns_type_to_af(key->type); + if (af >= 0) { + n = local_gateways(m->rtnl, ifindex, af, &addresses); + if (n < 0) + return n; + } + + return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), addresses, n); +} + +static int synthesize_gateway_ptr(Manager *m, int af, const union in_addr_union *address, int ifindex, DnsAnswer **answer) { + _cleanup_free_ struct local_address *addresses = NULL; + int n; + + assert(m); + assert(address); + assert(answer); + + n = local_gateways(m->rtnl, ifindex, af, &addresses); + if (n < 0) + return n; + + return answer_add_addresses_ptr(answer, "gateway", addresses, n, af, address); +} + +int dns_synthesize_answer( + Manager *m, + DnsQuestion *q, + int ifindex, + DnsAnswer **ret) { + + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + DnsResourceKey *key; + bool found = false; + int r; + + assert(m); + assert(q); + + DNS_QUESTION_FOREACH(key, q) { + union in_addr_union address; + const char *name; + int af; + + if (key->class != DNS_CLASS_IN && + key->class != DNS_CLASS_ANY) + continue; + + name = DNS_RESOURCE_KEY_NAME(key); + + if (is_localhost(name)) { + + r = synthesize_localhost_rr(m, key, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize localhost RRs: %m"); + + } else if (manager_is_own_hostname(m, name)) { + + r = synthesize_system_hostname_rr(m, key, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize system hostname RRs: %m"); + + } else if (is_gateway_hostname(name)) { + + r = synthesize_gateway_rr(m, key, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize gateway RRs: %m"); + + } else if ((dns_name_endswith(name, "127.in-addr.arpa") > 0 && dns_name_equal(name, "2.0.0.127.in-addr.arpa") == 0) || + dns_name_equal(name, "1.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) { + + r = synthesize_localhost_ptr(m, key, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize localhost PTR RRs: %m"); + + } else if (dns_name_address(name, &af, &address) > 0) { + + r = synthesize_system_hostname_ptr(m, af, &address, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize system hostname PTR RR: %m"); + + r = synthesize_gateway_ptr(m, af, &address, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize gateway hostname PTR RR: %m"); + } else + continue; + + found = true; + } + + r = found; + + if (ret) { + *ret = answer; + answer = NULL; + } + + return r; +} diff --git a/src/resolve/resolved-dns-synthesize.h b/src/resolve/resolved-dns-synthesize.h new file mode 100644 index 0000000000..5d829bb2e7 --- /dev/null +++ b/src/resolve/resolved-dns-synthesize.h @@ -0,0 +1,30 @@ +#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 "resolved-dns-answer.h" +#include "resolved-dns-question.h" +#include "resolved-manager.h" + +int dns_synthesize_ifindex(int ifindex); +int dns_synthesize_family(uint64_t flags); +DnsProtocol dns_synthesize_protocol(uint64_t flags); + +int dns_synthesize_answer(Manager *m, DnsQuestion *q, int ifindex, DnsAnswer **ret); diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 5640cd1d33..77f9ef0a83 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -24,6 +24,7 @@ #include "af-list.h" #include "alloc-util.h" #include "dns-domain.h" +#include "errno-list.h" #include "fd-util.h" #include "random-util.h" #include "resolved-dns-cache.h" @@ -43,6 +44,7 @@ static void dns_transaction_reset_answer(DnsTransaction *t) { t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; t->answer_authenticated = false; t->answer_nsec_ttl = (uint32_t) -1; + t->answer_errno = 0; } static void dns_transaction_flush_dnssec_transactions(DnsTransaction *t) { @@ -285,6 +287,7 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { DnsZoneItem *z; DnsTransaction *d; Iterator i; + const char *st; assert(t); assert(!DNS_TRANSACTION_IS_LIVE(state)); @@ -296,19 +299,26 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { "DNS_TRANSACTION=%" PRIu16, t->id, "DNS_QUESTION=%s", dns_transaction_key_string(t), "DNSSEC_RESULT=%s", dnssec_result_to_string(t->answer_dnssec_result), + "DNS_SERVER=%s", dns_server_string(t->server), + "DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(t->server->possible_feature_level), NULL); /* Note that this call might invalidate the query. Callers * should hence not attempt to access the query or transaction * after calling this function. */ + if (state == DNS_TRANSACTION_ERRNO) + st = errno_to_name(t->answer_errno); + else + st = dns_transaction_state_to_string(state); + 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), + st, t->answer_source < 0 ? "none" : dns_transaction_source_to_string(t->answer_source), t->answer_authenticated ? "authenticated" : "unsigned"); @@ -391,8 +401,10 @@ static void dns_transaction_retry(DnsTransaction *t) { dns_scope_next_dns_server(t->scope); r = dns_transaction_go(t); - if (r < 0) - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + if (r < 0) { + t->answer_errno = -r; + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); + } } static int dns_transaction_maybe_restart(DnsTransaction *t) { @@ -432,6 +444,13 @@ static int on_stream_complete(DnsStream *s, int error) { if (ERRNO_IS_DISCONNECT(error)) { usec_t usec; + if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { + /* If the LLMNR/TCP connection failed, the host doesn't support LLMNR, and we cannot answer the + * question on this scope. */ + dns_transaction_complete(t, DNS_TRANSACTION_NOT_FOUND); + return 0; + } + 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); @@ -440,7 +459,8 @@ static int on_stream_complete(DnsStream *s, int error) { return 0; } if (error != 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + t->answer_errno = error; + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); return 0; } @@ -655,30 +675,28 @@ static void dns_transaction_process_dnssec(DnsTransaction *t) { /* Are there ongoing DNSSEC transactions? If so, let's wait for them. */ r = dns_transaction_dnssec_ready(t); - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return; - } + if (r < 0) + goto fail; if (r == 0) /* We aren't ready yet (or one of our auxiliary transactions failed, and we shouldn't validate now */ return; /* See if we learnt things from the additional DNSSEC transactions, that we didn't know before, and better * restart the lookup immediately. */ r = dns_transaction_maybe_restart(t); - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return; - } + if (r < 0) + goto fail; if (r > 0) /* Transaction got restarted... */ 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); + if (r == -EBADMSG) { + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); return; } + if (r < 0) + goto fail; if (t->answer_dnssec_result == DNSSEC_INCOMPATIBLE_SERVER && t->scope->dnssec_mode == DNSSEC_YES) { @@ -697,12 +715,21 @@ static void dns_transaction_process_dnssec(DnsTransaction *t) { return; } + if (t->answer_dnssec_result == DNSSEC_INCOMPATIBLE_SERVER) + dns_server_warn_downgrade(t->server); + 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); + + return; + +fail: + t->answer_errno = -r; + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); } void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { @@ -846,10 +873,8 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { if (r < 0) { /* On LLMNR, if we cannot connect to the host, * we immediately give up */ - if (t->scope->protocol != DNS_PROTOCOL_DNS) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return; - } + if (t->scope->protocol != DNS_PROTOCOL_DNS) + goto fail; /* On DNS, couldn't send? Try immediately again, with a new server */ dns_transaction_retry(t); @@ -875,10 +900,8 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { /* See if we know things we didn't know before that indicate we better restart the lookup immediately. */ r = dns_transaction_maybe_restart(t); - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return; - } + if (r < 0) + goto fail; if (r > 0) /* Transaction got restarted... */ return; @@ -886,10 +909,8 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { /* Only consider responses with equivalent query section to the request */ r = dns_packet_is_reply_for(p, t->key); - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return; - } + if (r < 0) + goto fail; if (r == 0) { dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); return; @@ -918,10 +939,8 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { * quickly. */ if (t->state != DNS_TRANSACTION_PENDING) return; - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return; - } + if (r < 0) + goto fail; if (r > 0) { /* There are DNSSEC transactions pending now. Update the state accordingly. */ t->state = DNS_TRANSACTION_VALIDATING; @@ -932,6 +951,11 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { } dns_transaction_process_dnssec(t); + return; + +fail: + t->answer_errno = -r; + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); } static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { @@ -957,7 +981,8 @@ static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *use return 0; } if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); + t->answer_errno = -r; return 0; } @@ -1094,6 +1119,14 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { dns_transaction_stop_timeout(t); + r = dns_scope_network_good(t->scope); + if (r < 0) + return r; + if (r == 0) { + dns_transaction_complete(t, DNS_TRANSACTION_NETWORK_DOWN); + return 0; + } + if (t->n_attempts >= TRANSACTION_ATTEMPTS_MAX(t->scope->protocol)) { dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); return 0; @@ -1451,11 +1484,15 @@ int dns_transaction_go(DnsTransaction *t) { dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED); return 0; } + if (t->scope->protocol == DNS_PROTOCOL_LLMNR && ERRNO_IS_DISCONNECT(-r)) { + /* On LLMNR, if we cannot connect to a host via TCP when doing revers lookups. This means we cannot + * answer this request with this protocol. */ + dns_transaction_complete(t, DNS_TRANSACTION_NOT_FOUND); + return 0; + } if (r < 0) { - if (t->scope->protocol != DNS_PROTOCOL_DNS) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return 0; - } + if (t->scope->protocol != DNS_PROTOCOL_DNS) + return r; /* Couldn't send? Try immediately again, with a new server */ dns_scope_next_dns_server(t->scope); @@ -1679,33 +1716,13 @@ static int dns_transaction_is_primary_response(DnsTransaction *t, DnsResourceRec /* 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 */ + * CNAME/DNAME for it. */ 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; + return dns_resource_key_match_cname_or_dname(t->key, rr->key, NULL); } static bool dns_transaction_dnssec_supported(DnsTransaction *t) { @@ -2543,7 +2560,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { 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("Not validating response, server lacks DNSSEC support."); + log_debug("Not validating response for %" PRIu16 ", server lacks DNSSEC support.", t->id); return 0; } @@ -2648,7 +2665,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { if (r < 0) return r; - t->scope->manager->n_dnssec_secure++; + manager_dnssec_verdict(t->scope->manager, DNSSEC_SECURE, rr->key); /* Exit the loop, we dropped something from the answer, start from the beginning */ changed = true; @@ -2688,10 +2705,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { if (r < 0) return r; - if (authenticated) - t->scope->manager->n_dnssec_secure++; - else - t->scope->manager->n_dnssec_insecure++; + manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, rr->key); /* Exit the loop, we dropped something from the answer, start from the beginning */ changed = true; @@ -2710,7 +2724,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { if (r < 0) return r; - t->scope->manager->n_dnssec_insecure++; + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); changed = true; break; } @@ -2732,7 +2746,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { if (r < 0) return r; - t->scope->manager->n_dnssec_insecure++; + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); changed = true; break; } @@ -2758,7 +2772,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { if (r < 0) return r; - t->scope->manager->n_dnssec_insecure++; + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); changed = true; break; } @@ -2780,20 +2794,12 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { if (r < 0) return r; - t->scope->manager->n_dnssec_insecure++; + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); 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; @@ -2811,6 +2817,14 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { } if (r == 0) { + if (IN_SET(result, + DNSSEC_INVALID, + DNSSEC_SIGNATURE_EXPIRED, + DNSSEC_NO_SIGNATURE)) + manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, rr->key); + else /* DNSSEC_MISSING_KEY or DNSSEC_UNSUPPORTED_ALGORITHM */ + manager_dnssec_verdict(t->scope->manager, DNSSEC_INDETERMINATE, rr->key); + /* This is a primary response to our question, and it failed validation. That's * fatal. */ t->answer_dnssec_result = result; @@ -2892,6 +2906,8 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { t->answer_dnssec_result = DNSSEC_VALIDATED; t->answer_rcode = DNS_RCODE_NXDOMAIN; t->answer_authenticated = authenticated; + + manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, t->key); break; case DNSSEC_NSEC_NODATA: @@ -2900,6 +2916,8 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { t->answer_dnssec_result = DNSSEC_VALIDATED; t->answer_rcode = DNS_RCODE_SUCCESS; t->answer_authenticated = authenticated; + + manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, t->key); break; case DNSSEC_NSEC_OPTOUT: @@ -2907,6 +2925,8 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { 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; + + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, t->key); break; case DNSSEC_NSEC_NO_RR: @@ -2915,11 +2935,13 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { r = dns_transaction_requires_nsec(t); if (r < 0) return r; - if (r > 0) + if (r > 0) { t->answer_dnssec_result = DNSSEC_NO_SIGNATURE; - else { + manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, t->key); + } else { t->answer_dnssec_result = DNSSEC_UNSIGNED; t->answer_authenticated = false; + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, t->key); } break; @@ -2927,12 +2949,14 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { case DNSSEC_NSEC_UNSUPPORTED_ALGORITHM: /* We don't know the NSEC3 algorithm used? */ t->answer_dnssec_result = DNSSEC_UNSUPPORTED_ALGORITHM; + manager_dnssec_verdict(t->scope->manager, DNSSEC_INDETERMINATE, t->key); 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; + manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, t->key); break; default: @@ -2964,11 +2988,13 @@ static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] [DNS_TRANSACTION_TIMEOUT] = "timeout", [DNS_TRANSACTION_ATTEMPTS_MAX_REACHED] = "attempts-max-reached", [DNS_TRANSACTION_INVALID_REPLY] = "invalid-reply", - [DNS_TRANSACTION_RESOURCES] = "resources", + [DNS_TRANSACTION_ERRNO] = "errno", [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", + [DNS_TRANSACTION_NETWORK_DOWN] = "network-down", + [DNS_TRANSACTION_NOT_FOUND] = "not-found", }; 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 76cf6e71db..b6c5b2861c 100644 --- a/src/resolve/resolved-dns-transaction.h +++ b/src/resolve/resolved-dns-transaction.h @@ -35,11 +35,13 @@ enum DnsTransactionState { DNS_TRANSACTION_TIMEOUT, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, DNS_TRANSACTION_INVALID_REPLY, - DNS_TRANSACTION_RESOURCES, + DNS_TRANSACTION_ERRNO, DNS_TRANSACTION_ABORTED, DNS_TRANSACTION_DNSSEC_FAILED, DNS_TRANSACTION_NO_TRUST_ANCHOR, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED, + DNS_TRANSACTION_NETWORK_DOWN, + DNS_TRANSACTION_NOT_FOUND, /* like NXDOMAIN, but when LLMNR/TCP connections fail */ _DNS_TRANSACTION_STATE_MAX, _DNS_TRANSACTION_STATE_INVALID = -1 }; @@ -82,6 +84,7 @@ struct DnsTransaction { DnssecResult answer_dnssec_result; DnsTransactionSource answer_source; uint32_t answer_nsec_ttl; + int answer_errno; /* if state is DNS_TRANSACTION_ERRNO */ /* Indicates whether the primary answer is authenticated, * i.e. whether the RRs from answer which directly match the diff --git a/src/resolve/resolved-etc-hosts.c b/src/resolve/resolved-etc-hosts.c new file mode 100644 index 0000000000..467754774f --- /dev/null +++ b/src/resolve/resolved-etc-hosts.c @@ -0,0 +1,448 @@ +/*** + 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 "fd-util.h" +#include "fileio.h" +#include "hostname-util.h" +#include "resolved-etc-hosts.h" +#include "resolved-dns-synthesize.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" + +/* Recheck /etc/hosts at most once every 2s */ +#define ETC_HOSTS_RECHECK_USEC (2*USEC_PER_SEC) + +typedef struct EtcHostsItem { + int family; + union in_addr_union address; + + char **names; +} EtcHostsItem; + +typedef struct EtcHostsItemByName { + char *name; + + EtcHostsItem **items; + size_t n_items, n_allocated; +} EtcHostsItemByName; + +void manager_etc_hosts_flush(Manager *m) { + EtcHostsItem *item; + EtcHostsItemByName *bn; + + while ((item = set_steal_first(m->etc_hosts_by_address))) { + strv_free(item->names); + free(item); + } + + while ((bn = hashmap_steal_first(m->etc_hosts_by_name))) { + free(bn->name); + free(bn->items); + free(bn); + } + + m->etc_hosts_by_address = set_free(m->etc_hosts_by_address); + m->etc_hosts_by_name = hashmap_free(m->etc_hosts_by_name); + + m->etc_hosts_mtime = USEC_INFINITY; +} + +static void etc_hosts_item_hash_func(const void *p, struct siphash *state) { + const EtcHostsItem *item = p; + + siphash24_compress(&item->family, sizeof(item->family), state); + + if (item->family == AF_INET) + siphash24_compress(&item->address.in, sizeof(item->address.in), state); + else if (item->family == AF_INET6) + siphash24_compress(&item->address.in6, sizeof(item->address.in6), state); +} + +static int etc_hosts_item_compare_func(const void *a, const void *b) { + const EtcHostsItem *x = a, *y = b; + + if (x->family != x->family) + return x->family - y->family; + + if (x->family == AF_INET) + return memcmp(&x->address.in.s_addr, &y->address.in.s_addr, sizeof(struct in_addr)); + + if (x->family == AF_INET6) + return memcmp(&x->address.in6.s6_addr, &y->address.in6.s6_addr, sizeof(struct in6_addr)); + + return trivial_compare_func(a, b); +} + +static const struct hash_ops etc_hosts_item_ops = { + .hash = etc_hosts_item_hash_func, + .compare = etc_hosts_item_compare_func, +}; + +static int add_item(Manager *m, int family, const union in_addr_union *address, char **names) { + + EtcHostsItem key = { + .family = family, + .address = *address, + }; + EtcHostsItem *item; + char **n; + int r; + + assert(m); + assert(address); + + r = in_addr_is_null(family, address); + if (r < 0) + return r; + if (r > 0) + /* This is an 0.0.0.0 or :: item, which we assume means that we shall map the specified hostname to + * nothing. */ + item = NULL; + else { + /* If this is a normal address, then, simply add entry mapping it to the specified names */ + + item = set_get(m->etc_hosts_by_address, &key); + if (item) { + r = strv_extend_strv(&item->names, names, true); + if (r < 0) + return log_oom(); + } else { + + r = set_ensure_allocated(&m->etc_hosts_by_address, &etc_hosts_item_ops); + if (r < 0) + return log_oom(); + + item = new0(EtcHostsItem, 1); + if (!item) + return log_oom(); + + item->family = family; + item->address = *address; + item->names = names; + + r = set_put(m->etc_hosts_by_address, item); + if (r < 0) { + free(item); + return log_oom(); + } + } + } + + STRV_FOREACH(n, names) { + EtcHostsItemByName *bn; + + bn = hashmap_get(m->etc_hosts_by_name, *n); + if (!bn) { + r = hashmap_ensure_allocated(&m->etc_hosts_by_name, &dns_name_hash_ops); + if (r < 0) + return log_oom(); + + bn = new0(EtcHostsItemByName, 1); + if (!bn) + return log_oom(); + + bn->name = strdup(*n); + if (!bn->name) { + free(bn); + return log_oom(); + } + + r = hashmap_put(m->etc_hosts_by_name, bn->name, bn); + if (r < 0) { + free(bn->name); + free(bn); + return log_oom(); + } + } + + if (item) { + if (!GREEDY_REALLOC(bn->items, bn->n_allocated, bn->n_items+1)) + return log_oom(); + + bn->items[bn->n_items++] = item; + } + } + + return 0; +} + +static int parse_line(Manager *m, unsigned nr, const char *line) { + _cleanup_free_ char *address = NULL; + _cleanup_strv_free_ char **names = NULL; + union in_addr_union in; + bool suppressed = false; + int family, r; + + assert(m); + assert(line); + + r = extract_first_word(&line, &address, NULL, EXTRACT_RELAX); + if (r < 0) + return log_error_errno(r, "Couldn't extract address, in line /etc/hosts:%u.", nr); + if (r == 0) { + log_error("Premature end of line, in line /etc/hosts:%u.", nr); + return -EINVAL; + } + + r = in_addr_from_string_auto(address, &family, &in); + if (r < 0) + return log_error_errno(r, "Address '%s' is invalid, in line /etc/hosts:%u.", address, nr); + + for (;;) { + _cleanup_free_ char *name = NULL; + + r = extract_first_word(&line, &name, NULL, EXTRACT_RELAX); + if (r < 0) + return log_error_errno(r, "Couldn't extract host name, in line /etc/hosts:%u.", nr); + if (r == 0) + break; + + r = dns_name_is_valid(name); + if (r <= 0) + return log_error_errno(r, "Hostname %s is not valid, ignoring, in line /etc/hosts:%u.", name, nr); + + if (is_localhost(name)) { + /* Suppress the "localhost" line that is often seen */ + suppressed = true; + continue; + } + + r = strv_push(&names, name); + if (r < 0) + return log_oom(); + + name = NULL; + } + + if (strv_isempty(names)) { + + if (suppressed) + return 0; + + log_error("Line is missing any host names, in line /etc/hosts:%u.", nr); + return -EINVAL; + } + + /* Takes possession of the names strv */ + r = add_item(m, family, &in, names); + if (r < 0) + return r; + + names = NULL; + return r; +} + +int manager_etc_hosts_read(Manager *m) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + struct stat st; + usec_t ts; + unsigned nr = 0; + int r; + + assert_se(sd_event_now(m->event, clock_boottime_or_monotonic(), &ts) >= 0); + + /* See if we checked /etc/hosts recently already */ + if (m->etc_hosts_last != USEC_INFINITY && m->etc_hosts_last + ETC_HOSTS_RECHECK_USEC > ts) + return 0; + + m->etc_hosts_last = ts; + + if (m->etc_hosts_mtime != USEC_INFINITY) { + if (stat("/etc/hosts", &st) < 0) { + if (errno == ENOENT) { + r = 0; + goto clear; + } + + return log_error_errno(errno, "Failed to stat /etc/hosts: %m"); + } + + /* Did the mtime change? If not, there's no point in re-reading the file. */ + if (timespec_load(&st.st_mtim) == m->etc_hosts_mtime) + return 0; + } + + f = fopen("/etc/hosts", "re"); + if (!f) { + if (errno == ENOENT) { + r = 0; + goto clear; + } + + return log_error_errno(errno, "Failed to open /etc/hosts: %m"); + } + + /* Take the timestamp at the beginning of processing, so that any changes made later are read on the next + * invocation */ + r = fstat(fileno(f), &st); + if (r < 0) + return log_error_errno(errno, "Failed to fstat() /etc/hosts: %m"); + + manager_etc_hosts_flush(m); + + FOREACH_LINE(line, f, return log_error_errno(errno, "Failed to read /etc/hosts: %m")) { + char *l; + + nr ++; + + l = strstrip(line); + if (isempty(l)) + continue; + if (l[0] == '#') + continue; + + r = parse_line(m, nr, l); + if (r == -ENOMEM) /* On OOM we abandon the half-built-up structure. All other errors we ignore and proceed */ + goto clear; + } + + m->etc_hosts_mtime = timespec_load(&st.st_mtim); + m->etc_hosts_last = ts; + + return 1; + +clear: + manager_etc_hosts_flush(m); + return r; +} + +int manager_etc_hosts_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer) { + bool found_a = false, found_aaaa = false; + EtcHostsItemByName *bn; + EtcHostsItem k = {}; + DnsResourceKey *t; + const char *name; + unsigned i; + int r; + + assert(m); + assert(q); + assert(answer); + + r = manager_etc_hosts_read(m); + if (r < 0) + return r; + + name = dns_question_first_name(q); + if (!name) + return 0; + + r = dns_name_address(name, &k.family, &k.address); + if (r > 0) { + EtcHostsItem *item; + DnsResourceKey *found_ptr = NULL; + + item = set_get(m->etc_hosts_by_address, &k); + if (!item) + return 0; + + /* We have an address in /etc/hosts that matches the queried name. Let's return successful. Actual data + * we'll only return if the request was for PTR. */ + + DNS_QUESTION_FOREACH(t, q) { + if (!IN_SET(t->type, DNS_TYPE_PTR, DNS_TYPE_ANY)) + continue; + if (!IN_SET(t->class, DNS_CLASS_IN, DNS_CLASS_ANY)) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(t), name); + if (r < 0) + return r; + if (r > 0) { + found_ptr = t; + break; + } + } + + if (found_ptr) { + char **n; + + r = dns_answer_reserve(answer, strv_length(item->names)); + if (r < 0) + return r; + + STRV_FOREACH(n, item->names) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + rr = dns_resource_record_new(found_ptr); + if (!rr) + return -ENOMEM; + + rr->ptr.name = strdup(*n); + if (!rr->ptr.name) + return -ENOMEM; + + r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + } + + return 1; + } + + bn = hashmap_get(m->etc_hosts_by_name, name); + if (!bn) + return 0; + + r = dns_answer_reserve(answer, bn->n_items); + if (r < 0) + return r; + + DNS_QUESTION_FOREACH(t, q) { + if (!IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_AAAA, DNS_TYPE_ANY)) + continue; + if (!IN_SET(t->class, DNS_CLASS_IN, DNS_CLASS_ANY)) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(t), name); + if (r < 0) + return r; + if (r == 0) + continue; + + if (IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_ANY)) + found_a = true; + if (IN_SET(t->type, DNS_TYPE_AAAA, DNS_TYPE_ANY)) + found_aaaa = true; + + if (found_a && found_aaaa) + break; + } + + for (i = 0; i < bn->n_items; i++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + if ((found_a && bn->items[i]->family != AF_INET) && + (found_aaaa && bn->items[i]->family != AF_INET6)) + continue; + + r = dns_resource_record_new_address(&rr, bn->items[i]->family, &bn->items[i]->address, bn->name); + if (r < 0) + return r; + + r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 1; +} diff --git a/src/resolve/resolved-etc-hosts.h b/src/resolve/resolved-etc-hosts.h new file mode 100644 index 0000000000..9d5a175f18 --- /dev/null +++ b/src/resolve/resolved-etc-hosts.h @@ -0,0 +1,28 @@ +#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 "resolved-manager.h" +#include "resolved-dns-question.h" +#include "resolved-dns-answer.h" + +void manager_etc_hosts_flush(Manager *m); +int manager_etc_hosts_read(Manager *m); +int manager_etc_hosts_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer); diff --git a/src/resolve/resolved-link-bus.c b/src/resolve/resolved-link-bus.c index 20352a3e51..2ca7ece851 100644 --- a/src/resolve/resolved-link-bus.c +++ b/src/resolve/resolved-link-bus.c @@ -392,7 +392,7 @@ int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, v if (r < 0) return r; if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid search negative trust anchor domain: %s", *i); + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid negative trust anchor domain: %s", *i); } ns = set_new(&dns_name_hash_ops); diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index b203f19dbb..e2f9c8b400 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -49,6 +49,7 @@ int link_new(Manager *m, Link **ret, int ifindex) { l->llmnr_support = RESOLVE_SUPPORT_YES; l->mdns_support = RESOLVE_SUPPORT_NO; l->dnssec_mode = _DNSSEC_MODE_INVALID; + l->operstate = IF_OPER_UNKNOWN; r = hashmap_put(m->links, INT_TO_PTR(ifindex), l); if (r < 0) @@ -177,7 +178,8 @@ int link_update_rtnl(Link *l, sd_netlink_message *m) { if (r < 0) return r; - sd_netlink_message_read_u32(m, IFLA_MTU, &l->mtu); + (void) sd_netlink_message_read_u32(m, IFLA_MTU, &l->mtu); + (void) sd_netlink_message_read_u8(m, IFLA_OPERSTATE, &l->operstate); if (sd_netlink_message_read_string(m, IFLA_IFNAME, &n) >= 0) { strncpy(l->name, n, sizeof(l->name)-1); @@ -514,7 +516,12 @@ bool link_relevant(Link *l, int family, bool multicast) { return false; } - sd_network_link_get_operational_state(l->ifindex, &state); + /* Check kernel operstate + * https://www.kernel.org/doc/Documentation/networking/operstates.txt */ + if (!IN_SET(l->operstate, IF_OPER_UNKNOWN, IF_OPER_UP)) + return false; + + (void) sd_network_link_get_operational_state(l->ifindex, &state); if (state && !STR_IN_SET(state, "unknown", "degraded", "routable")) return false; diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h index 6544214b77..3b6aafb8f0 100644 --- a/src/resolve/resolved-link.h +++ b/src/resolve/resolved-link.h @@ -82,6 +82,7 @@ struct Link { char name[IF_NAMESIZE]; uint32_t mtu; + uint8_t operstate; }; int link_new(Manager *m, Link **ret, int ifindex); diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index d6d75a3f78..704f012c37 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -37,10 +37,11 @@ #include "random-util.h" #include "resolved-bus.h" #include "resolved-conf.h" +#include "resolved-etc-hosts.h" #include "resolved-llmnr.h" #include "resolved-manager.h" -#include "resolved-resolv-conf.h" #include "resolved-mdns.h" +#include "resolved-resolv-conf.h" #include "socket-util.h" #include "string-table.h" #include "string-util.h" @@ -485,6 +486,7 @@ int manager_new(Manager **ret) { m->dnssec_mode = DNSSEC_NO; m->read_resolv_conf = true; m->need_builtin_fallbacks = true; + m->etc_hosts_last = m->etc_hosts_mtime = USEC_INFINITY; r = dns_trust_anchor_load(&m->trust_anchor); if (r < 0) @@ -594,6 +596,7 @@ Manager *manager_free(Manager *m) { free(m->mdns_hostname); dns_trust_anchor_flush(&m->trust_anchor); + manager_etc_hosts_flush(m); free(m); @@ -1203,3 +1206,19 @@ bool manager_dnssec_supported(Manager *m) { return true; } + +void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key) { + + assert(verdict >= 0); + assert(verdict < _DNSSEC_VERDICT_MAX); + + if (log_get_max_level() >= LOG_DEBUG) { + _cleanup_free_ char *s = NULL; + + (void) dns_resource_key_to_string(key, &s); + + log_debug("Found verdict for lookup %s: %s", s ? strstrip(s) : "n/a", dnssec_verdict_to_string(verdict)); + } + + m->n_dnssec_verdict[verdict]++; +} diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index 8b13074298..a4291d57ff 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -123,7 +123,12 @@ struct Manager { sd_event_source *sigusr1_event_source; unsigned n_transactions_total; - unsigned n_dnssec_secure, n_dnssec_insecure, n_dnssec_bogus, n_dnssec_indeterminate; + unsigned n_dnssec_verdict[_DNSSEC_VERDICT_MAX]; + + /* Data from /etc/hosts */ + Set* etc_hosts_by_address; + Hashmap* etc_hosts_by_name; + usec_t etc_hosts_last, etc_hosts_mtime; }; /* Manager */ @@ -161,3 +166,5 @@ int manager_compile_search_domains(Manager *m, OrderedSet **domains); DnssecMode manager_get_dnssec_mode(Manager *m); bool manager_dnssec_supported(Manager *m); + +void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key); diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 0199f28eba..94c99c4d91 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -3153,6 +3153,7 @@ static int check_unit_generic(int code, const char *good_states, char **args) { sd_bus *bus; char **name; int r; + bool found = false; r = acquire_bus(BUS_MANAGER, &bus); if (r < 0) @@ -3168,11 +3169,13 @@ static int check_unit_generic(int code, const char *good_states, char **args) { state = check_one_unit(bus, *name, good_states, arg_quiet); if (state < 0) return state; - if (state == 0) - r = code; + if (state > 0) + found = true; } - return r; + /* use the given return code for the case that we won't find + * any unit which matches the list */ + return found ? 0 : code; } static int check_unit_active(int argc, char *argv[], void *userdata) { diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h index 1183df6105..814263546b 100644 --- a/src/systemd/sd-messages.h +++ b/src/systemd/sd-messages.h @@ -88,6 +88,7 @@ _SD_BEGIN_DECLARATIONS; #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) +#define SD_MESSAGE_DNSSEC_DOWNGRADE SD_ID128_MAKE(36,db,2d,fa,5a,90,45,e1,bd,4a,f5,f9,3e,1c,f0,57) _SD_END_DECLARATIONS; diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c index e83b8b1c12..104d5111c5 100644 --- a/src/udev/udev-builtin-net_id.c +++ b/src/udev/udev-builtin-net_id.c @@ -106,6 +106,8 @@ #include "string-util.h" #include "udev.h" +#define ONBOARD_INDEX_MAX (16*1024-1) + enum netname_type{ NET_UNDEF, NET_PCI, @@ -152,6 +154,13 @@ static int dev_pci_onboard(struct udev_device *dev, struct netnames *names) { if (idx <= 0) return -EINVAL; + /* Some BIOSes report rubbish indexes that are excessively high (2^24-1 is an index VMware likes to report for + * example). Let's define a cut-off where we don't consider the index reliable anymore. We pick some arbitrary + * cut-off, which is somewhere beyond the realistic number of physical network interface a system might + * have. Ideally the kernel would already filter his crap for us, but it doesn't currently. */ + if (idx > ONBOARD_INDEX_MAX) + return -ENOENT; + /* kernel provided port index for multiple ports on a single PCI function */ attr = udev_device_get_sysattr_value(dev, "dev_port"); if (attr) diff --git a/src/udev/udevd.c b/src/udev/udevd.c index 8627a81ec2..e658d6a079 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -1652,7 +1652,7 @@ exit: int main(int argc, char *argv[]) { _cleanup_free_ char *cgroup = NULL; - _cleanup_close_ int fd_ctrl = -1, fd_uevent = -1; + int fd_ctrl = -1, fd_uevent = -1; int r; log_set_target(LOG_TARGET_AUTO); |