diff options
Diffstat (limited to 'src/resolve')
78 files changed, 0 insertions, 30431 deletions
diff --git a/src/resolve/.gitignore b/src/resolve/.gitignore deleted file mode 100644 index f0835923b7..0000000000 --- a/src/resolve/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -/resolved-gperf.c -/resolved.conf -/dns_type-from-name.gperf -/dns_type-from-name.h -/dns_type-list.txt -/dns_type-to-name.h diff --git a/src/resolve/Makefile b/src/resolve/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/resolve/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile
\ No newline at end of file diff --git a/src/resolve/RFCs b/src/resolve/RFCs deleted file mode 100644 index 09c85f9518..0000000000 --- a/src/resolve/RFCs +++ /dev/null @@ -1,59 +0,0 @@ -Y = Comprehensively Implemented, to the point appropriate for resolved -D = Comprehensively Implemented, by a dependency of resolved -! = Missing and something we might want to implement -~ = Needs no explicit support or doesn't apply -? = Is this relevant today? - = We are working on this - -Y https://tools.ietf.org/html/rfc1034 → DOMAIN NAMES - CONCEPTS AND FACILITIES -Y https://tools.ietf.org/html/rfc1035 → DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION -? https://tools.ietf.org/html/rfc1101 → DNS Encoding of Network Names and Other Types -Y https://tools.ietf.org/html/rfc1123 → Requirements for Internet Hosts — Application and Support -~ https://tools.ietf.org/html/rfc1464 → Using the Domain Name System To Store Arbitrary String Attributes -Y https://tools.ietf.org/html/rfc1536 → Common DNS Implementation Errors and Suggested Fixes -Y https://tools.ietf.org/html/rfc1876 → A Means for Expressing Location Information in the Domain Name System -Y https://tools.ietf.org/html/rfc2181 → Clarifications to the DNS Specification -Y https://tools.ietf.org/html/rfc2308 → Negative Caching of DNS Queries (DNS NCACHE) -Y https://tools.ietf.org/html/rfc2782 → A DNS RR for specifying the location of services (DNS SRV) -D https://tools.ietf.org/html/rfc3492 → Punycode: A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA) -Y https://tools.ietf.org/html/rfc3596 → DNS Extensions to Support IP Version 6 -Y https://tools.ietf.org/html/rfc3597 → Handling of Unknown DNS Resource Record (RR) Types -Y https://tools.ietf.org/html/rfc4033 → DNS Security Introduction and Requirements -Y https://tools.ietf.org/html/rfc4034 → Resource Records for the DNS Security Extensions -Y https://tools.ietf.org/html/rfc4035 → Protocol Modifications for the DNS Security Extensions -! https://tools.ietf.org/html/rfc4183 → A Suggested Scheme for DNS Resolution of Networks and Gateways -Y https://tools.ietf.org/html/rfc4255 → Using DNS to Securely Publish Secure Shell (SSH) Key Fingerprints -Y https://tools.ietf.org/html/rfc4343 → Domain Name System (DNS) Case Insensitivity Clarification -~ https://tools.ietf.org/html/rfc4470 → Minimally Covering NSEC Records and DNSSEC On-line Signing -Y https://tools.ietf.org/html/rfc4501 → Domain Name System Uniform Resource Identifiers -Y https://tools.ietf.org/html/rfc4509 → Use of SHA-256 in DNSSEC Delegation Signer (DS) Resource Records (RRs) -~ https://tools.ietf.org/html/rfc4592 → The Role of Wildcards in the Domain Name System -~ https://tools.ietf.org/html/rfc4697 → Observed DNS Resolution Misbehavior -Y https://tools.ietf.org/html/rfc4795 → Link-Local Multicast Name Resolution (LLMNR) -Y https://tools.ietf.org/html/rfc5011 → Automated Updates of DNS Security (DNSSEC) Trust Anchors -Y https://tools.ietf.org/html/rfc5155 → DNS Security (DNSSEC) Hashed Authenticated Denial of Existence -Y https://tools.ietf.org/html/rfc5452 → Measures for Making DNS More Resilient against Forged Answers -Y https://tools.ietf.org/html/rfc5702 → Use of SHA-2 Algorithms with RSA in DNSKEY and RRSIG Resource Records for DNSSEC -Y https://tools.ietf.org/html/rfc5890 → Internationalized Domain Names for Applications (IDNA): Definitions and Document Framework -Y https://tools.ietf.org/html/rfc5891 → Internationalized Domain Names in Applications (IDNA): Protocol -Y https://tools.ietf.org/html/rfc5966 → DNS Transport over TCP - Implementation Requirements -Y https://tools.ietf.org/html/rfc6303 → Locally Served DNS Zones -Y https://tools.ietf.org/html/rfc6604 → xNAME RCODE and Status Bits Clarification -Y https://tools.ietf.org/html/rfc6605 → Elliptic Curve Digital Signature Algorithm (DSA) for DNSSEC - https://tools.ietf.org/html/rfc6672 → DNAME Redirection in the DNS -! https://tools.ietf.org/html/rfc6731 → Improved Recursive DNS Server Selection for Multi-Interfaced Nodes -Y https://tools.ietf.org/html/rfc6761 → Special-Use Domain Names - https://tools.ietf.org/html/rfc6762 → Multicast DNS - https://tools.ietf.org/html/rfc6763 → DNS-Based Service Discovery -~ https://tools.ietf.org/html/rfc6781 → DNSSEC Operational Practices, Version 2 -Y https://tools.ietf.org/html/rfc6840 → Clarifications and Implementation Notes for DNS Security (DNSSEC) -Y https://tools.ietf.org/html/rfc6891 → Extension Mechanisms for DNS (EDNS(0)) -Y https://tools.ietf.org/html/rfc6944 → Applicability Statement: DNS Security (DNSSEC) DNSKEY Algorithm Implementation Status -Y https://tools.ietf.org/html/rfc6975 → Signaling Cryptographic Algorithm Understanding in DNS Security Extensions (DNSSEC) -Y https://tools.ietf.org/html/rfc7129 → Authenticated Denial of Existence in the DNS -Y https://tools.ietf.org/html/rfc7646 → Definition and Use of DNSSEC Negative Trust Anchors -~ https://tools.ietf.org/html/rfc7719 → DNS Terminology - -Also relevant: - - https://www.iab.org/documents/correspondence-reports-documents/2013-2/iab-statement-dotless-domains-considered-harmful/ diff --git a/src/resolve/dns-type.c b/src/resolve/dns-type.c deleted file mode 100644 index d89ae28dcd..0000000000 --- a/src/resolve/dns-type.c +++ /dev/null @@ -1,332 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Zbigniew Jędrzejewski-Szmek - - 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 <sys/socket.h> - -#include "dns-type.h" -#include "parse-util.h" -#include "string-util.h" - -typedef const struct { - uint16_t type; - const char *name; -} dns_type; - -static const struct dns_type_name * -lookup_dns_type (register const char *str, register GPERF_LEN_TYPE len); - -#include "dns_type-from-name.h" -#include "dns_type-to-name.h" - -int dns_type_from_string(const char *s) { - const struct dns_type_name *sc; - - assert(s); - - sc = lookup_dns_type(s, strlen(s)); - if (sc) - return sc->id; - - s = startswith_no_case(s, "TYPE"); - if (s) { - unsigned x; - - if (safe_atou(s, &x) >= 0 && - x <= UINT16_MAX) - return (int) x; - } - - return _DNS_TYPE_INVALID; -} - -bool dns_type_is_pseudo(uint16_t type) { - - /* Checks whether the specified type is a "pseudo-type". What - * a "pseudo-type" precisely is, is defined only very weakly, - * but apparently entails all RR types that are not actually - * stored as RRs on the server and should hence also not be - * cached. We use this list primarily to validate NSEC type - * bitfields, and to verify what to cache. */ - - return IN_SET(type, - 0, /* A Pseudo RR type, according to RFC 2931 */ - DNS_TYPE_ANY, - DNS_TYPE_AXFR, - DNS_TYPE_IXFR, - DNS_TYPE_OPT, - DNS_TYPE_TSIG, - DNS_TYPE_TKEY - ); -} - -bool dns_class_is_pseudo(uint16_t class) { - return class == DNS_TYPE_ANY; -} - -bool dns_type_is_valid_query(uint16_t type) { - - /* The types valid as questions in packets */ - - return !IN_SET(type, - 0, - DNS_TYPE_OPT, - DNS_TYPE_TSIG, - DNS_TYPE_TKEY, - - /* RRSIG are technically valid as questions, but we refuse doing explicit queries for them, as - * they aren't really payload, but signatures for payload, and cannot be validated on their - * own. After all they are the signatures, and have no signatures of their own validating - * them. */ - DNS_TYPE_RRSIG); -} - -bool dns_type_is_zone_transer(uint16_t type) { - - /* Zone transfers, either normal or incremental */ - - return IN_SET(type, - DNS_TYPE_AXFR, - DNS_TYPE_IXFR); -} - -bool dns_type_is_valid_rr(uint16_t type) { - - /* The types valid as RR in packets (but not necessarily - * stored on servers). */ - - return !IN_SET(type, - DNS_TYPE_ANY, - DNS_TYPE_AXFR, - DNS_TYPE_IXFR); -} - -bool dns_class_is_valid_rr(uint16_t class) { - return class != DNS_CLASS_ANY; -} - -bool dns_type_may_redirect(uint16_t type) { - /* The following record types should never be redirected using - * CNAME/DNAME RRs. See - * <https://tools.ietf.org/html/rfc4035#section-2.5>. */ - - if (dns_type_is_pseudo(type)) - return false; - - return !IN_SET(type, - DNS_TYPE_CNAME, - DNS_TYPE_DNAME, - DNS_TYPE_NSEC3, - DNS_TYPE_NSEC, - DNS_TYPE_RRSIG, - DNS_TYPE_NXT, - DNS_TYPE_SIG, - DNS_TYPE_KEY); -} - -bool dns_type_may_wildcard(uint16_t type) { - - /* The following records may not be expanded from wildcard RRsets */ - - if (dns_type_is_pseudo(type)) - return false; - - return !IN_SET(type, - DNS_TYPE_NSEC3, - DNS_TYPE_SOA, - - /* Prohibited by https://tools.ietf.org/html/rfc4592#section-4.4 */ - DNS_TYPE_DNAME); -} - -bool dns_type_apex_only(uint16_t type) { - - /* Returns true for all RR types that may only appear signed in a zone apex */ - - return IN_SET(type, - DNS_TYPE_SOA, - DNS_TYPE_NS, /* this one can appear elsewhere, too, but not signed */ - DNS_TYPE_DNSKEY, - DNS_TYPE_NSEC3PARAM); -} - -bool dns_type_is_dnssec(uint16_t type) { - return IN_SET(type, - DNS_TYPE_DS, - DNS_TYPE_DNSKEY, - DNS_TYPE_RRSIG, - DNS_TYPE_NSEC, - DNS_TYPE_NSEC3, - DNS_TYPE_NSEC3PARAM); -} - -bool dns_type_is_obsolete(uint16_t type) { - return IN_SET(type, - /* Obsoleted by RFC 973 */ - DNS_TYPE_MD, - DNS_TYPE_MF, - DNS_TYPE_MAILA, - - /* Kinda obsoleted by RFC 2505 */ - DNS_TYPE_MB, - DNS_TYPE_MG, - DNS_TYPE_MR, - DNS_TYPE_MINFO, - DNS_TYPE_MAILB, - - /* RFC1127 kinda obsoleted this by recommending against its use */ - DNS_TYPE_WKS, - - /* Declared historical by RFC 6563 */ - DNS_TYPE_A6, - - /* Obsoleted by DNSSEC-bis */ - DNS_TYPE_NXT, - - /* RFC 1035 removed support for concepts that needed this from RFC 883 */ - DNS_TYPE_NULL); -} - -bool dns_type_needs_authentication(uint16_t type) { - - /* Returns true for all (non-obsolete) RR types where records are not useful if they aren't - * authenticated. I.e. everything that contains crypto keys. */ - - return IN_SET(type, - DNS_TYPE_CERT, - DNS_TYPE_SSHFP, - DNS_TYPE_IPSECKEY, - DNS_TYPE_DS, - DNS_TYPE_DNSKEY, - DNS_TYPE_TLSA, - DNS_TYPE_CDNSKEY, - DNS_TYPE_OPENPGPKEY, - DNS_TYPE_CAA); -} - -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) { - - case DNS_CLASS_IN: - return "IN"; - - case DNS_CLASS_ANY: - return "ANY"; - } - - return NULL; -} - -int dns_class_from_string(const char *s) { - - if (!s) - return _DNS_CLASS_INVALID; - - if (strcaseeq(s, "IN")) - return DNS_CLASS_IN; - else if (strcaseeq(s, "ANY")) - return DNS_CLASS_ANY; - - return _DNS_CLASS_INVALID; -} - -const char* tlsa_cert_usage_to_string(uint8_t cert_usage) { - - switch (cert_usage) { - - case 0: - return "CA constraint"; - - case 1: - return "Service certificate constraint"; - - case 2: - return "Trust anchor assertion"; - - case 3: - return "Domain-issued certificate"; - - case 4 ... 254: - return "Unassigned"; - - case 255: - return "Private use"; - } - - return NULL; /* clang cannot count that we covered everything */ -} - -const char* tlsa_selector_to_string(uint8_t selector) { - switch (selector) { - - case 0: - return "Full Certificate"; - - case 1: - return "SubjectPublicKeyInfo"; - - case 2 ... 254: - return "Unassigned"; - - case 255: - return "Private use"; - } - - return NULL; -} - -const char* tlsa_matching_type_to_string(uint8_t selector) { - - switch (selector) { - - case 0: - return "No hash used"; - - case 1: - return "SHA-256"; - - case 2: - return "SHA-512"; - - case 3 ... 254: - return "Unassigned"; - - case 255: - return "Private use"; - } - - return NULL; -} diff --git a/src/resolve/dns-type.h b/src/resolve/dns-type.h deleted file mode 100644 index e675fe4ea3..0000000000 --- a/src/resolve/dns-type.h +++ /dev/null @@ -1,162 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Zbigniew Jędrzejewski-Szmek - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include "macro.h" - -/* DNS record types, taken from - * http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml. - */ -enum { - /* Normal records */ - DNS_TYPE_A = 0x01, - DNS_TYPE_NS, - DNS_TYPE_MD, - DNS_TYPE_MF, - DNS_TYPE_CNAME, - DNS_TYPE_SOA, - DNS_TYPE_MB, - DNS_TYPE_MG, - DNS_TYPE_MR, - DNS_TYPE_NULL, - DNS_TYPE_WKS, - DNS_TYPE_PTR, - DNS_TYPE_HINFO, - DNS_TYPE_MINFO, - DNS_TYPE_MX, - DNS_TYPE_TXT, - DNS_TYPE_RP, - DNS_TYPE_AFSDB, - DNS_TYPE_X25, - DNS_TYPE_ISDN, - DNS_TYPE_RT, - DNS_TYPE_NSAP, - DNS_TYPE_NSAP_PTR, - DNS_TYPE_SIG, - DNS_TYPE_KEY, - DNS_TYPE_PX, - DNS_TYPE_GPOS, - DNS_TYPE_AAAA, - DNS_TYPE_LOC, - DNS_TYPE_NXT, - DNS_TYPE_EID, - DNS_TYPE_NIMLOC, - DNS_TYPE_SRV, - DNS_TYPE_ATMA, - DNS_TYPE_NAPTR, - DNS_TYPE_KX, - DNS_TYPE_CERT, - DNS_TYPE_A6, - DNS_TYPE_DNAME, - DNS_TYPE_SINK, - DNS_TYPE_OPT, /* EDNS0 option */ - DNS_TYPE_APL, - DNS_TYPE_DS, - DNS_TYPE_SSHFP, - DNS_TYPE_IPSECKEY, - DNS_TYPE_RRSIG, - DNS_TYPE_NSEC, - DNS_TYPE_DNSKEY, - DNS_TYPE_DHCID, - DNS_TYPE_NSEC3, - DNS_TYPE_NSEC3PARAM, - DNS_TYPE_TLSA, - - DNS_TYPE_HIP = 0x37, - DNS_TYPE_NINFO, - DNS_TYPE_RKEY, - DNS_TYPE_TALINK, - DNS_TYPE_CDS, - DNS_TYPE_CDNSKEY, - DNS_TYPE_OPENPGPKEY, - - DNS_TYPE_SPF = 0x63, - DNS_TYPE_NID, - DNS_TYPE_L32, - DNS_TYPE_L64, - DNS_TYPE_LP, - DNS_TYPE_EUI48, - DNS_TYPE_EUI64, - - DNS_TYPE_TKEY = 0xF9, - DNS_TYPE_TSIG, - DNS_TYPE_IXFR, - DNS_TYPE_AXFR, - DNS_TYPE_MAILB, - DNS_TYPE_MAILA, - DNS_TYPE_ANY, - DNS_TYPE_URI, - DNS_TYPE_CAA, - DNS_TYPE_TA = 0x8000, - DNS_TYPE_DLV, - - _DNS_TYPE_MAX, - _DNS_TYPE_INVALID = -1 -}; - -assert_cc(DNS_TYPE_SSHFP == 44); -assert_cc(DNS_TYPE_TLSA == 52); -assert_cc(DNS_TYPE_ANY == 255); - -/* DNS record classes, see RFC 1035 */ -enum { - DNS_CLASS_IN = 0x01, - DNS_CLASS_ANY = 0xFF, - - _DNS_CLASS_MAX, - _DNS_CLASS_INVALID = -1 -}; - -#define _DNS_CLASS_STRING_MAX (sizeof "CLASS" + DECIMAL_STR_MAX(uint16_t)) -#define _DNS_TYPE_STRING_MAX (sizeof "CLASS" + DECIMAL_STR_MAX(uint16_t)) - -bool dns_type_is_pseudo(uint16_t type); -bool dns_type_is_valid_query(uint16_t type); -bool dns_type_is_valid_rr(uint16_t type); -bool dns_type_may_redirect(uint16_t type); -bool dns_type_is_dnssec(uint16_t type); -bool dns_type_is_obsolete(uint16_t type); -bool dns_type_may_wildcard(uint16_t type); -bool dns_type_apex_only(uint16_t type); -bool dns_type_needs_authentication(uint16_t type); -bool dns_type_is_zone_transer(uint16_t type); -int dns_type_to_af(uint16_t type); - -bool dns_class_is_pseudo(uint16_t class); -bool dns_class_is_valid_rr(uint16_t class); - -/* TYPE?? follows http://tools.ietf.org/html/rfc3597#section-5 */ -const char *dns_type_to_string(int type); -int dns_type_from_string(const char *s); - -const char *dns_class_to_string(uint16_t class); -int dns_class_from_string(const char *name); - -/* https://tools.ietf.org/html/draft-ietf-dane-protocol-23#section-7.2 */ -const char *tlsa_cert_usage_to_string(uint8_t cert_usage); - -/* https://tools.ietf.org/html/draft-ietf-dane-protocol-23#section-7.3 */ -const char *tlsa_selector_to_string(uint8_t selector); - -/* https://tools.ietf.org/html/draft-ietf-dane-protocol-23#section-7.4 */ -const char *tlsa_matching_type_to_string(uint8_t selector); - -/* https://tools.ietf.org/html/rfc6844#section-5.1 */ -#define CAA_FLAG_CRITICAL (1u << 7) diff --git a/src/resolve/org.freedesktop.resolve1.conf b/src/resolve/org.freedesktop.resolve1.conf deleted file mode 100644 index 25b09774e5..0000000000 --- a/src/resolve/org.freedesktop.resolve1.conf +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0"?> <!--*-nxml-*--> -<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" - "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> - -<!-- - This file is part of systemd. - - 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. ---> - -<busconfig> - - <policy user="systemd-resolve"> - <allow own="org.freedesktop.resolve1"/> - <allow send_destination="org.freedesktop.resolve1"/> - <allow receive_sender="org.freedesktop.resolve1"/> - </policy> - - <policy context="default"> - <allow send_destination="org.freedesktop.resolve1"/> - <allow receive_sender="org.freedesktop.resolve1"/> - </policy> - -</busconfig> diff --git a/src/resolve/org.freedesktop.resolve1.service b/src/resolve/org.freedesktop.resolve1.service deleted file mode 100644 index 7ac5c323f0..0000000000 --- a/src/resolve/org.freedesktop.resolve1.service +++ /dev/null @@ -1,12 +0,0 @@ -# This file is part of systemd. -# -# 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. - -[D-BUS Service] -Name=org.freedesktop.resolve1 -Exec=/bin/false -User=root -SystemdService=dbus-org.freedesktop.resolve1.service diff --git a/src/resolve/resolv.conf b/src/resolve/resolv.conf deleted file mode 100644 index b8034d6829..0000000000 --- a/src/resolve/resolv.conf +++ /dev/null @@ -1,11 +0,0 @@ -# This is a static resolv.conf file for connecting local clients to -# systemd-resolved via its DNS stub listener on 127.0.0.53. -# -# Third party programs must not access this file directly, but only through the -# symlink at /etc/resolv.conf. To manage resolv.conf(5) in a different way, -# replace this symlink by a static file or a different symlink. -# -# See systemd-resolved.service(8) for details about the supported modes of -# operation for /etc/resolv.conf. - -nameserver 127.0.0.53 diff --git a/src/resolve/resolve-tool.c b/src/resolve/resolve-tool.c deleted file mode 100644 index 9d4d04220c..0000000000 --- a/src/resolve/resolve-tool.c +++ /dev/null @@ -1,2025 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Zbigniew Jędrzejewski-Szmek - - 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 <getopt.h> -#include <net/if.h> - -#include "sd-bus.h" -#include "sd-netlink.h" - -#include "af-list.h" -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "escape.h" -#include "gcrypt-util.h" -#include "in-addr-util.h" -#include "netlink-util.h" -#include "pager.h" -#include "parse-util.h" -#include "resolved-def.h" -#include "resolved-dns-packet.h" -#include "strv.h" -#include "terminal-util.h" - -#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) - -static int arg_family = AF_UNSPEC; -static int arg_ifindex = 0; -static uint16_t arg_type = 0; -static uint16_t arg_class = 0; -static bool arg_legend = true; -static uint64_t arg_flags = 0; -static bool arg_no_pager = false; - -typedef enum ServiceFamily { - SERVICE_FAMILY_TCP, - SERVICE_FAMILY_UDP, - SERVICE_FAMILY_SCTP, - _SERVICE_FAMILY_INVALID = -1, -} ServiceFamily; -static ServiceFamily arg_service_family = SERVICE_FAMILY_TCP; - -typedef enum RawType { - RAW_NONE, - RAW_PAYLOAD, - RAW_PACKET, -} RawType; -static RawType arg_raw = RAW_NONE; - -static enum { - MODE_RESOLVE_HOST, - MODE_RESOLVE_RECORD, - MODE_RESOLVE_SERVICE, - MODE_RESOLVE_OPENPGP, - MODE_RESOLVE_TLSA, - MODE_STATISTICS, - MODE_RESET_STATISTICS, - MODE_FLUSH_CACHES, - MODE_STATUS, -} arg_mode = MODE_RESOLVE_HOST; - -static ServiceFamily service_family_from_string(const char *s) { - if (s == NULL || streq(s, "tcp")) - return SERVICE_FAMILY_TCP; - if (streq(s, "udp")) - return SERVICE_FAMILY_UDP; - if (streq(s, "sctp")) - return SERVICE_FAMILY_SCTP; - return _SERVICE_FAMILY_INVALID; -} - -static const char* service_family_to_string(ServiceFamily service) { - switch(service) { - case SERVICE_FAMILY_TCP: - return "_tcp"; - case SERVICE_FAMILY_UDP: - return "_udp"; - case SERVICE_FAMILY_SCTP: - return "_sctp"; - default: - assert_not_reached("invalid service"); - } -} - -static void print_source(uint64_t flags, usec_t rtt) { - char rtt_str[FORMAT_TIMESTAMP_MAX]; - - if (!arg_legend) - return; - - if (flags == 0) - return; - - fputs("\n-- Information acquired via", stdout); - - if (flags != 0) - printf(" protocol%s%s%s%s%s", - flags & SD_RESOLVED_DNS ? " DNS" :"", - flags & SD_RESOLVED_LLMNR_IPV4 ? " LLMNR/IPv4" : "", - flags & SD_RESOLVED_LLMNR_IPV6 ? " LLMNR/IPv6" : "", - flags & SD_RESOLVED_MDNS_IPV4 ? "mDNS/IPv4" : "", - flags & SD_RESOLVED_MDNS_IPV6 ? "mDNS/IPv6" : ""); - - assert_se(format_timespan(rtt_str, sizeof(rtt_str), rtt, 100)); - - printf(" in %s", rtt_str); - - fputc('.', stdout); - fputc('\n', stdout); - - printf("-- Data is authenticated: %s\n", yes_no(flags & SD_RESOLVED_AUTHENTICATED)); -} - -static int resolve_host(sd_bus *bus, const char *name) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *canonical = NULL; - char ifname[IF_NAMESIZE] = ""; - unsigned c = 0; - int r; - uint64_t flags; - usec_t ts; - - assert(name); - - if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) - return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); - - log_debug("Resolving %s (family %s, interface %s).", name, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveHostname"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "isit", arg_ifindex, name, arg_family, arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) - return log_error_errno(r, "%s: resolve call failed: %s", name, bus_error_message(&error, r)); - - ts = now(CLOCK_MONOTONIC) - ts; - - r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { - _cleanup_free_ char *pretty = NULL; - int ifindex, family; - const void *a; - size_t sz; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "ii", &ifindex, &family); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &a, &sz); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (!IN_SET(family, AF_INET, AF_INET6)) { - log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown"); - continue; - } - - if (sz != FAMILY_ADDRESS_SIZE(family)) { - log_error("%s: systemd-resolved returned address of invalid size %zu for family %s", name, sz, af_to_name(family) ?: "unknown"); - return -EINVAL; - } - - ifname[0] = 0; - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - r = in_addr_ifindex_to_string(family, a, ifindex, &pretty); - if (r < 0) - return log_error_errno(r, "Failed to print address for %s: %m", name); - - printf("%*s%s %s%s%s\n", - (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", - pretty, - isempty(ifname) ? "" : "%", ifname); - - c++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "st", &canonical, &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (!streq(name, canonical)) - printf("%*s%s (%s)\n", - (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", - canonical); - - if (c == 0) { - log_error("%s: no addresses found", name); - return -ESRCH; - } - - print_source(flags, ts); - - return 0; -} - -static int resolve_address(sd_bus *bus, int family, const union in_addr_union *address, int ifindex) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *pretty = NULL; - char ifname[IF_NAMESIZE] = ""; - uint64_t flags; - unsigned c = 0; - usec_t ts; - int r; - - assert(bus); - assert(IN_SET(family, AF_INET, AF_INET6)); - assert(address); - - if (ifindex <= 0) - ifindex = arg_ifindex; - - r = in_addr_ifindex_to_string(family, address, ifindex, &pretty); - if (r < 0) - return log_oom(); - - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - log_debug("Resolving %s%s%s.", pretty, isempty(ifname) ? "" : "%", ifname); - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveAddress"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "ii", ifindex, family); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_array(req, 'y', address, FAMILY_ADDRESS_SIZE(family)); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "t", arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) { - log_error("%s: resolve call failed: %s", pretty, bus_error_message(&error, r)); - return r; - } - - ts = now(CLOCK_MONOTONIC) - ts; - - r = sd_bus_message_enter_container(reply, 'a', "(is)"); - if (r < 0) - return bus_log_create_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "is")) > 0) { - const char *n; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "is", &ifindex, &n); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return r; - - ifname[0] = 0; - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - printf("%*s%*s%*s%s %s\n", - (int) strlen(pretty), c == 0 ? pretty : "", - isempty(ifname) ? 0 : 1, c > 0 || isempty(ifname) ? "" : "%", - (int) strlen(ifname), c == 0 ? ifname : "", - c == 0 ? ":" : " ", - n); - - c++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "t", &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (c == 0) { - log_error("%s: no names found", pretty); - return -ESRCH; - } - - print_source(flags, ts); - - return 0; -} - -static int output_rr_packet(const void *d, size_t l, int ifindex) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - int r; - char ifname[IF_NAMESIZE] = ""; - - r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0); - if (r < 0) - return log_oom(); - - p->refuse_compression = true; - - r = dns_packet_append_blob(p, d, l, NULL); - if (r < 0) - return log_oom(); - - r = dns_packet_read_rr(p, &rr, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to parse RR: %m"); - - if (arg_raw == RAW_PAYLOAD) { - void *data; - ssize_t k; - - k = dns_resource_record_payload(rr, &data); - if (k < 0) - return log_error_errno(k, "Cannot dump RR: %m"); - fwrite(data, 1, k, stdout); - } else { - const char *s; - - s = dns_resource_record_to_string(rr); - if (!s) - return log_oom(); - - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - printf("%s%s%s\n", s, isempty(ifname) ? "" : " # interface ", ifname); - } - - return 0; -} - -static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_t type, bool warn_missing) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - char ifname[IF_NAMESIZE] = ""; - unsigned n = 0; - uint64_t flags; - int r; - usec_t ts; - bool needs_authentication = false; - - assert(name); - - if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) - return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); - - log_debug("Resolving %s %s %s (interface %s).", name, dns_class_to_string(class), dns_type_to_string(type), isempty(ifname) ? "*" : ifname); - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveRecord"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "isqqt", arg_ifindex, name, class, type, arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) { - if (warn_missing || r != -ENXIO) - log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r)); - return r; - } - - ts = now(CLOCK_MONOTONIC) - ts; - - r = sd_bus_message_enter_container(reply, 'a', "(iqqay)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "iqqay")) > 0) { - uint16_t c, t; - int ifindex; - const void *d; - size_t l; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "iqq", &ifindex, &c, &t); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &d, &l); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (arg_raw == RAW_PACKET) { - uint64_t u64 = htole64(l); - - fwrite(&u64, sizeof(u64), 1, stdout); - fwrite(d, 1, l, stdout); - } else { - r = output_rr_packet(d, l, ifindex); - if (r < 0) - return r; - } - - if (dns_type_needs_authentication(t)) - needs_authentication = true; - - n++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "t", &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (n == 0) { - if (warn_missing) - log_error("%s: no records found", name); - return -ESRCH; - } - - print_source(flags, ts); - - if ((flags & SD_RESOLVED_AUTHENTICATED) == 0 && needs_authentication) { - fflush(stdout); - - fprintf(stderr, "\n%s" - "WARNING: The resources shown contain cryptographic key data which could not be\n" - " authenticated. It is not suitable to authenticate any communication.\n" - " This is usually indication that DNSSEC authentication was not enabled\n" - " or is not available for the selected protocol or DNS servers.%s\n", - ansi_highlight_red(), - ansi_normal()); - } - - return 0; -} - -static int resolve_rfc4501(sd_bus *bus, const char *name) { - uint16_t type = 0, class = 0; - const char *p, *q, *n; - int r; - - assert(bus); - assert(name); - assert(startswith(name, "dns:")); - - /* Parse RFC 4501 dns: URIs */ - - p = name + 4; - - if (p[0] == '/') { - const char *e; - - if (p[1] != '/') - goto invalid; - - e = strchr(p + 2, '/'); - if (!e) - goto invalid; - - if (e != p + 2) - log_warning("DNS authority specification not supported; ignoring specified authority."); - - p = e + 1; - } - - q = strchr(p, '?'); - if (q) { - n = strndupa(p, q - p); - q++; - - for (;;) { - const char *f; - - f = startswith_no_case(q, "class="); - if (f) { - _cleanup_free_ char *t = NULL; - const char *e; - - if (class != 0) { - log_error("DNS class specified twice."); - return -EINVAL; - } - - e = strchrnul(f, ';'); - t = strndup(f, e - f); - if (!t) - return log_oom(); - - r = dns_class_from_string(t); - if (r < 0) { - log_error("Unknown DNS class %s.", t); - return -EINVAL; - } - - class = r; - - if (*e == ';') { - q = e + 1; - continue; - } - - break; - } - - f = startswith_no_case(q, "type="); - if (f) { - _cleanup_free_ char *t = NULL; - const char *e; - - if (type != 0) { - log_error("DNS type specified twice."); - return -EINVAL; - } - - e = strchrnul(f, ';'); - t = strndup(f, e - f); - if (!t) - return log_oom(); - - r = dns_type_from_string(t); - if (r < 0) { - log_error("Unknown DNS type %s.", t); - return -EINVAL; - } - - type = r; - - if (*e == ';') { - q = e + 1; - continue; - } - - break; - } - - goto invalid; - } - } else - n = p; - - if (class == 0) - class = arg_class ?: DNS_CLASS_IN; - if (type == 0) - type = arg_type ?: DNS_TYPE_A; - - return resolve_record(bus, n, class, type, true); - -invalid: - log_error("Invalid DNS URI: %s", name); - return -EINVAL; -} - -static int resolve_service(sd_bus *bus, const char *name, const char *type, const char *domain) { - const char *canonical_name, *canonical_type, *canonical_domain; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - char ifname[IF_NAMESIZE] = ""; - size_t indent, sz; - uint64_t flags; - const char *p; - unsigned c; - usec_t ts; - int r; - - assert(bus); - assert(domain); - - name = empty_to_null(name); - type = empty_to_null(type); - - if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) - return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); - - if (name) - log_debug("Resolving service \"%s\" of type %s in %s (family %s, interface %s).", name, type, domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); - else if (type) - log_debug("Resolving service type %s of %s (family %s, interface %s).", type, domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); - else - log_debug("Resolving service type %s (family %s, interface %s).", domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveService"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "isssit", arg_ifindex, name, type, domain, arg_family, arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) - return log_error_errno(r, "Resolve call failed: %s", bus_error_message(&error, r)); - - ts = now(CLOCK_MONOTONIC) - ts; - - r = sd_bus_message_enter_container(reply, 'a', "(qqqsa(iiay)s)"); - if (r < 0) - return bus_log_parse_error(r); - - indent = - (name ? strlen(name) + 1 : 0) + - (type ? strlen(type) + 1 : 0) + - strlen(domain) + 2; - - c = 0; - while ((r = sd_bus_message_enter_container(reply, 'r', "qqqsa(iiay)s")) > 0) { - uint16_t priority, weight, port; - const char *hostname, *canonical; - - r = sd_bus_message_read(reply, "qqqs", &priority, &weight, &port, &hostname); - if (r < 0) - return bus_log_parse_error(r); - - if (name) - printf("%*s%s", (int) strlen(name), c == 0 ? name : "", c == 0 ? "/" : " "); - if (type) - printf("%*s%s", (int) strlen(type), c == 0 ? type : "", c == 0 ? "/" : " "); - - printf("%*s%s %s:%u [priority=%u, weight=%u]\n", - (int) strlen(domain), c == 0 ? domain : "", - c == 0 ? ":" : " ", - hostname, port, - priority, weight); - - r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { - _cleanup_free_ char *pretty = NULL; - int ifindex, family; - const void *a; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "ii", &ifindex, &family); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &a, &sz); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (!IN_SET(family, AF_INET, AF_INET6)) { - log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown"); - continue; - } - - if (sz != FAMILY_ADDRESS_SIZE(family)) { - log_error("%s: systemd-resolved returned address of invalid size %zu for family %s", name, sz, af_to_name(family) ?: "unknown"); - return -EINVAL; - } - - ifname[0] = 0; - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - r = in_addr_to_string(family, a, &pretty); - if (r < 0) - return log_error_errno(r, "Failed to print address for %s: %m", name); - - printf("%*s%s%s%s\n", (int) indent, "", pretty, isempty(ifname) ? "" : "%s", ifname); - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "s", &canonical); - if (r < 0) - return bus_log_parse_error(r); - - if (!streq(hostname, canonical)) - printf("%*s(%s)\n", (int) indent, "", canonical); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - c++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_enter_container(reply, 'a', "ay"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read_array(reply, 'y', (const void**) &p, &sz)) > 0) { - _cleanup_free_ char *escaped = NULL; - - escaped = cescape_length(p, sz); - if (!escaped) - return log_oom(); - - printf("%*s%s\n", (int) indent, "", escaped); - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "ssst", &canonical_name, &canonical_type, &canonical_domain, &flags); - if (r < 0) - return bus_log_parse_error(r); - - canonical_name = empty_to_null(canonical_name); - canonical_type = empty_to_null(canonical_type); - - if (!streq_ptr(name, canonical_name) || - !streq_ptr(type, canonical_type) || - !streq_ptr(domain, canonical_domain)) { - - printf("%*s(", (int) indent, ""); - - if (canonical_name) - printf("%s/", canonical_name); - if (canonical_type) - printf("%s/", canonical_type); - - printf("%s)\n", canonical_domain); - } - - print_source(flags, ts); - - return 0; -} - -static int resolve_openpgp(sd_bus *bus, const char *address) { - const char *domain, *full; - int r; - _cleanup_free_ char *hashed = NULL; - - assert(bus); - assert(address); - - domain = strrchr(address, '@'); - if (!domain) { - log_error("Address does not contain '@': \"%s\"", address); - return -EINVAL; - } else if (domain == address || domain[1] == '\0') { - log_error("Address starts or ends with '@': \"%s\"", address); - return -EINVAL; - } - domain++; - - r = string_hashsum_sha256(address, domain - 1 - address, &hashed); - if (r < 0) - return log_error_errno(r, "Hashing failed: %m"); - - strshorten(hashed, 56); - - full = strjoina(hashed, "._openpgpkey.", domain); - log_debug("Looking up \"%s\".", full); - - r = resolve_record(bus, full, - arg_class ?: DNS_CLASS_IN, - arg_type ?: DNS_TYPE_OPENPGPKEY, false); - - if (IN_SET(r, -ENXIO, -ESRCH)) { /* NXDOMAIN or NODATA? */ - hashed = NULL; - r = string_hashsum_sha224(address, domain - 1 - address, &hashed); - if (r < 0) - return log_error_errno(r, "Hashing failed: %m"); - - full = strjoina(hashed, "._openpgpkey.", domain); - log_debug("Looking up \"%s\".", full); - - return resolve_record(bus, full, - arg_class ?: DNS_CLASS_IN, - arg_type ?: DNS_TYPE_OPENPGPKEY, true); - } - - return r; -} - -static int resolve_tlsa(sd_bus *bus, const char *address) { - const char *port; - uint16_t port_num = 443; - _cleanup_free_ char *full = NULL; - int r; - - assert(bus); - assert(address); - - port = strrchr(address, ':'); - if (port) { - r = safe_atou16(port + 1, &port_num); - if (r < 0 || port_num == 0) - return log_error_errno(r, "Invalid port \"%s\".", port + 1); - - address = strndupa(address, port - address); - } - - r = asprintf(&full, "_%u.%s.%s", - port_num, - service_family_to_string(arg_service_family), - address); - if (r < 0) - return log_oom(); - - log_debug("Looking up \"%s\".", full); - - return resolve_record(bus, full, - arg_class ?: DNS_CLASS_IN, - arg_type ?: DNS_TYPE_TLSA, true); -} - -static int show_statistics(sd_bus *bus) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - uint64_t n_current_transactions, n_total_transactions, - cache_size, n_cache_hit, n_cache_miss, - n_dnssec_secure, n_dnssec_insecure, n_dnssec_bogus, n_dnssec_indeterminate; - int r, dnssec_supported; - - assert(bus); - - r = sd_bus_get_property_trivial(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "DNSSECSupported", - &error, - 'b', - &dnssec_supported); - if (r < 0) - return log_error_errno(r, "Failed to get DNSSEC supported state: %s", bus_error_message(&error, r)); - - printf("DNSSEC supported by current servers: %s%s%s\n\n", - ansi_highlight(), - yes_no(dnssec_supported), - ansi_normal()); - - r = sd_bus_get_property(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "TransactionStatistics", - &error, - &reply, - "(tt)"); - if (r < 0) - return log_error_errno(r, "Failed to get transaction statistics: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "(tt)", - &n_current_transactions, - &n_total_transactions); - if (r < 0) - return bus_log_parse_error(r); - - printf("%sTransactions%s\n" - "Current Transactions: %" PRIu64 "\n" - " Total Transactions: %" PRIu64 "\n", - ansi_highlight(), - ansi_normal(), - n_current_transactions, - n_total_transactions); - - reply = sd_bus_message_unref(reply); - - r = sd_bus_get_property(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "CacheStatistics", - &error, - &reply, - "(ttt)"); - if (r < 0) - return log_error_errno(r, "Failed to get cache statistics: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "(ttt)", - &cache_size, - &n_cache_hit, - &n_cache_miss); - if (r < 0) - return bus_log_parse_error(r); - - printf("\n%sCache%s\n" - " Current Cache Size: %" PRIu64 "\n" - " Cache Hits: %" PRIu64 "\n" - " Cache Misses: %" PRIu64 "\n", - ansi_highlight(), - ansi_normal(), - cache_size, - n_cache_hit, - n_cache_miss); - - reply = sd_bus_message_unref(reply); - - r = sd_bus_get_property(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "DNSSECStatistics", - &error, - &reply, - "(tttt)"); - if (r < 0) - return log_error_errno(r, "Failed to get DNSSEC statistics: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "(tttt)", - &n_dnssec_secure, - &n_dnssec_insecure, - &n_dnssec_bogus, - &n_dnssec_indeterminate); - if (r < 0) - return bus_log_parse_error(r); - - printf("\n%sDNSSEC Verdicts%s\n" - " Secure: %" PRIu64 "\n" - " Insecure: %" PRIu64 "\n" - " Bogus: %" PRIu64 "\n" - " Indeterminate: %" PRIu64 "\n", - ansi_highlight(), - ansi_normal(), - n_dnssec_secure, - n_dnssec_insecure, - n_dnssec_bogus, - n_dnssec_indeterminate); - - return 0; -} - -static int reset_statistics(sd_bus *bus) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - r = sd_bus_call_method(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResetStatistics", - &error, - NULL, - NULL); - if (r < 0) - return log_error_errno(r, "Failed to reset statistics: %s", bus_error_message(&error, r)); - - return 0; -} - -static int flush_caches(sd_bus *bus) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - r = sd_bus_call_method(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "FlushCaches", - &error, - NULL, - NULL); - if (r < 0) - return log_error_errno(r, "Failed to flush caches: %s", bus_error_message(&error, r)); - - return 0; -} - -static int map_link_dns_servers(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { - char ***l = userdata; - int r; - - assert(bus); - assert(member); - assert(m); - assert(l); - - r = sd_bus_message_enter_container(m, 'a', "(iay)"); - if (r < 0) - return r; - - for (;;) { - const void *a; - char *pretty; - int family; - size_t sz; - - r = sd_bus_message_enter_container(m, 'r', "iay"); - if (r < 0) - return r; - if (r == 0) - break; - - r = sd_bus_message_read(m, "i", &family); - if (r < 0) - return r; - - r = sd_bus_message_read_array(m, 'y', &a, &sz); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - if (!IN_SET(family, AF_INET, AF_INET6)) { - log_debug("Unexpected family, ignoring."); - continue; - } - - if (sz != FAMILY_ADDRESS_SIZE(family)) { - log_debug("Address size mismatch, ignoring."); - continue; - } - - r = in_addr_to_string(family, a, &pretty); - if (r < 0) - return r; - - r = strv_consume(l, pretty); - if (r < 0) - return r; - } - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - return 0; -} - -static int map_link_domains(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { - char ***l = userdata; - int r; - - assert(bus); - assert(member); - assert(m); - assert(l); - - r = sd_bus_message_enter_container(m, 'a', "(sb)"); - if (r < 0) - return r; - - for (;;) { - const char *domain; - int route_only; - char *pretty; - - r = sd_bus_message_read(m, "(sb)", &domain, &route_only); - if (r < 0) - return r; - if (r == 0) - break; - - if (route_only) - pretty = strappend("~", domain); - else - pretty = strdup(domain); - if (!pretty) - return -ENOMEM; - - r = strv_consume(l, pretty); - if (r < 0) - return r; - } - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - return 0; -} - -static int status_ifindex(sd_bus *bus, int ifindex, const char *name, bool *empty_line) { - - struct link_info { - uint64_t scopes_mask; - char *llmnr; - char *mdns; - char *dnssec; - char **dns; - char **domains; - char **ntas; - int dnssec_supported; - } link_info = {}; - - static const struct bus_properties_map property_map[] = { - { "ScopesMask", "t", NULL, offsetof(struct link_info, scopes_mask) }, - { "DNS", "a(iay)", map_link_dns_servers, offsetof(struct link_info, dns) }, - { "Domains", "a(sb)", map_link_domains, offsetof(struct link_info, domains) }, - { "LLMNR", "s", NULL, offsetof(struct link_info, llmnr) }, - { "MulticastDNS", "s", NULL, offsetof(struct link_info, mdns) }, - { "DNSSEC", "s", NULL, offsetof(struct link_info, dnssec) }, - { "DNSSECNegativeTrustAnchors", "as", NULL, offsetof(struct link_info, ntas) }, - { "DNSSECSupported", "b", NULL, offsetof(struct link_info, dnssec_supported) }, - {} - }; - - _cleanup_free_ char *ifi = NULL, *p = NULL; - char ifname[IF_NAMESIZE] = ""; - char **i; - int r; - - assert(bus); - assert(ifindex > 0); - assert(empty_line); - - if (!name) { - if (!if_indextoname(ifindex, ifname)) - return log_error_errno(errno, "Failed to resolve interface name for %i: %m", ifindex); - - name = ifname; - } - - if (asprintf(&ifi, "%i", ifindex) < 0) - return log_oom(); - - r = sd_bus_path_encode("/org/freedesktop/resolve1/link", ifi, &p); - if (r < 0) - return log_oom(); - - r = bus_map_all_properties(bus, - "org.freedesktop.resolve1", - p, - property_map, - &link_info); - if (r < 0) { - log_error_errno(r, "Failed to get link data for %i: %m", ifindex); - goto finish; - } - - pager_open(arg_no_pager, false); - - if (*empty_line) - fputc('\n', stdout); - - printf("%sLink %i (%s)%s\n", - ansi_highlight(), ifindex, name, ansi_normal()); - - if (link_info.scopes_mask == 0) - printf(" Current Scopes: none\n"); - else - printf(" Current Scopes:%s%s%s%s%s\n", - link_info.scopes_mask & SD_RESOLVED_DNS ? " DNS" : "", - link_info.scopes_mask & SD_RESOLVED_LLMNR_IPV4 ? " LLMNR/IPv4" : "", - link_info.scopes_mask & SD_RESOLVED_LLMNR_IPV6 ? " LLMNR/IPv6" : "", - link_info.scopes_mask & SD_RESOLVED_MDNS_IPV4 ? " mDNS/IPv4" : "", - link_info.scopes_mask & SD_RESOLVED_MDNS_IPV6 ? " mDNS/IPv6" : ""); - - printf(" LLMNR setting: %s\n" - "MulticastDNS setting: %s\n" - " DNSSEC setting: %s\n" - " DNSSEC supported: %s\n", - strna(link_info.llmnr), - strna(link_info.mdns), - strna(link_info.dnssec), - yes_no(link_info.dnssec_supported)); - - STRV_FOREACH(i, link_info.dns) { - printf(" %s %s\n", - i == link_info.dns ? "DNS Servers:" : " ", - *i); - } - - STRV_FOREACH(i, link_info.domains) { - printf(" %s %s\n", - i == link_info.domains ? "DNS Domain:" : " ", - *i); - } - - STRV_FOREACH(i, link_info.ntas) { - printf(" %s %s\n", - i == link_info.ntas ? "DNSSEC NTA:" : " ", - *i); - } - - *empty_line = true; - - r = 0; - -finish: - strv_free(link_info.dns); - strv_free(link_info.domains); - free(link_info.llmnr); - free(link_info.mdns); - free(link_info.dnssec); - strv_free(link_info.ntas); - return r; -} - -static int map_global_dns_servers(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { - char ***l = userdata; - int r; - - assert(bus); - assert(member); - assert(m); - assert(l); - - r = sd_bus_message_enter_container(m, 'a', "(iiay)"); - if (r < 0) - return r; - - for (;;) { - const void *a; - char *pretty; - int family, ifindex; - size_t sz; - - r = sd_bus_message_enter_container(m, 'r', "iiay"); - if (r < 0) - return r; - if (r == 0) - break; - - r = sd_bus_message_read(m, "ii", &ifindex, &family); - if (r < 0) - return r; - - r = sd_bus_message_read_array(m, 'y', &a, &sz); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - if (ifindex != 0) /* only show the global ones here */ - continue; - - if (!IN_SET(family, AF_INET, AF_INET6)) { - log_debug("Unexpected family, ignoring."); - continue; - } - - if (sz != FAMILY_ADDRESS_SIZE(family)) { - log_debug("Address size mismatch, ignoring."); - continue; - } - - r = in_addr_to_string(family, a, &pretty); - if (r < 0) - return r; - - r = strv_consume(l, pretty); - if (r < 0) - return r; - } - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - return 0; -} - -static int map_global_domains(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { - char ***l = userdata; - int r; - - assert(bus); - assert(member); - assert(m); - assert(l); - - r = sd_bus_message_enter_container(m, 'a', "(isb)"); - if (r < 0) - return r; - - for (;;) { - const char *domain; - int route_only, ifindex; - char *pretty; - - r = sd_bus_message_read(m, "(isb)", &ifindex, &domain, &route_only); - if (r < 0) - return r; - if (r == 0) - break; - - if (ifindex != 0) /* only show the global ones here */ - continue; - - if (route_only) - pretty = strappend("~", domain); - else - pretty = strdup(domain); - if (!pretty) - return -ENOMEM; - - r = strv_consume(l, pretty); - if (r < 0) - return r; - } - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - return 0; -} - -static int status_global(sd_bus *bus, bool *empty_line) { - - struct global_info { - char **dns; - char **domains; - char **ntas; - } global_info = {}; - - static const struct bus_properties_map property_map[] = { - { "DNS", "a(iiay)", map_global_dns_servers, offsetof(struct global_info, dns) }, - { "Domains", "a(isb)", map_global_domains, offsetof(struct global_info, domains) }, - { "DNSSECNegativeTrustAnchors", "as", NULL, offsetof(struct global_info, ntas) }, - {} - }; - - char **i; - int r; - - assert(bus); - assert(empty_line); - - r = bus_map_all_properties(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - property_map, - &global_info); - if (r < 0) { - log_error_errno(r, "Failed to get global data: %m"); - goto finish; - } - - if (strv_isempty(global_info.dns) && strv_isempty(global_info.domains) && strv_isempty(global_info.ntas)) { - r = 0; - goto finish; - } - - pager_open(arg_no_pager, false); - - printf("%sGlobal%s\n", ansi_highlight(), ansi_normal()); - STRV_FOREACH(i, global_info.dns) { - printf(" %s %s\n", - i == global_info.dns ? "DNS Servers:" : " ", - *i); - } - - STRV_FOREACH(i, global_info.domains) { - printf(" %s %s\n", - i == global_info.domains ? "DNS Domain:" : " ", - *i); - } - - strv_sort(global_info.ntas); - STRV_FOREACH(i, global_info.ntas) { - printf(" %s %s\n", - i == global_info.ntas ? "DNSSEC NTA:" : " ", - *i); - } - - *empty_line = true; - - r = 0; - -finish: - strv_free(global_info.dns); - strv_free(global_info.domains); - strv_free(global_info.ntas); - - return r; -} - -static int status_all(sd_bus *bus) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - sd_netlink_message *i; - bool empty_line = false; - int r; - - assert(bus); - - r = status_global(bus, &empty_line); - if (r < 0) - return r; - - r = sd_netlink_open(&rtnl); - if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); - - r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, 0); - if (r < 0) - return rtnl_log_create_error(r); - - r = sd_netlink_message_request_dump(req, true); - if (r < 0) - return rtnl_log_create_error(r); - - r = sd_netlink_call(rtnl, req, 0, &reply); - if (r < 0) - return log_error_errno(r, "Failed to enumerate links: %m"); - - r = 0; - for (i = reply; i; i = sd_netlink_message_next(i)) { - const char *name; - int ifindex, q; - uint16_t type; - - q = sd_netlink_message_get_type(i, &type); - if (q < 0) - return rtnl_log_parse_error(q); - - if (type != RTM_NEWLINK) - continue; - - q = sd_rtnl_message_link_get_ifindex(i, &ifindex); - if (q < 0) - return rtnl_log_parse_error(q); - - if (ifindex == LOOPBACK_IFINDEX) - continue; - - q = sd_netlink_message_read_string(i, IFLA_IFNAME, &name); - if (q < 0) - return rtnl_log_parse_error(q); - - q = status_ifindex(bus, ifindex, name, &empty_line); - if (q < 0 && r >= 0) - r = q; - } - - return r; -} - -static void help_protocol_types(void) { - if (arg_legend) - puts("Known protocol types:"); - puts("dns\nllmnr\nllmnr-ipv4\nllmnr-ipv6"); -} - -static void help_dns_types(void) { - const char *t; - int i; - - if (arg_legend) - puts("Known DNS RR types:"); - for (i = 0; i < _DNS_TYPE_MAX; i++) { - t = dns_type_to_string(i); - if (t) - puts(t); - } -} - -static void help_dns_classes(void) { - const char *t; - int i; - - if (arg_legend) - puts("Known DNS RR classes:"); - for (i = 0; i < _DNS_CLASS_MAX; i++) { - t = dns_class_to_string(i); - if (t) - puts(t); - } -} - -static void help(void) { - printf("%1$s [OPTIONS...] HOSTNAME|ADDRESS...\n" - "%1$s [OPTIONS...] --service [[NAME] TYPE] DOMAIN\n" - "%1$s [OPTIONS...] --openpgp EMAIL@DOMAIN...\n" - "%1$s [OPTIONS...] --statistics\n" - "%1$s [OPTIONS...] --reset-statistics\n" - "\n" - "Resolve domain names, IPv4 and IPv6 addresses, DNS records, and services.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " -4 Resolve IPv4 addresses\n" - " -6 Resolve IPv6 addresses\n" - " -i --interface=INTERFACE Look on interface\n" - " -p --protocol=PROTO|help Look via protocol\n" - " -t --type=TYPE|help Query RR with DNS type\n" - " -c --class=CLASS|help Query RR with DNS class\n" - " --service Resolve service (SRV)\n" - " --service-address=BOOL Resolve address for services (default: yes)\n" - " --service-txt=BOOL Resolve TXT records for services (default: yes)\n" - " --openpgp Query OpenPGP public key\n" - " --tlsa Query TLS public key\n" - " --cname=BOOL Follow CNAME redirects (default: yes)\n" - " --search=BOOL Use search domains for single-label names\n" - " (default: yes)\n" - " --raw[=payload|packet] Dump the answer as binary data\n" - " --legend=BOOL Print headers and additional info (default: yes)\n" - " --statistics Show resolver statistics\n" - " --reset-statistics Reset resolver statistics\n" - " --status Show link and server status\n" - " --flush-caches Flush all local DNS caches\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_LEGEND, - ARG_SERVICE, - ARG_CNAME, - ARG_SERVICE_ADDRESS, - ARG_SERVICE_TXT, - ARG_OPENPGP, - ARG_TLSA, - ARG_RAW, - ARG_SEARCH, - ARG_STATISTICS, - ARG_RESET_STATISTICS, - ARG_STATUS, - ARG_FLUSH_CACHES, - ARG_NO_PAGER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "type", required_argument, NULL, 't' }, - { "class", required_argument, NULL, 'c' }, - { "legend", required_argument, NULL, ARG_LEGEND }, - { "interface", required_argument, NULL, 'i' }, - { "protocol", required_argument, NULL, 'p' }, - { "cname", required_argument, NULL, ARG_CNAME }, - { "service", no_argument, NULL, ARG_SERVICE }, - { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS }, - { "service-txt", required_argument, NULL, ARG_SERVICE_TXT }, - { "openpgp", no_argument, NULL, ARG_OPENPGP }, - { "tlsa", optional_argument, NULL, ARG_TLSA }, - { "raw", optional_argument, NULL, ARG_RAW }, - { "search", required_argument, NULL, ARG_SEARCH }, - { "statistics", no_argument, NULL, ARG_STATISTICS, }, - { "reset-statistics", no_argument, NULL, ARG_RESET_STATISTICS }, - { "status", no_argument, NULL, ARG_STATUS }, - { "flush-caches", no_argument, NULL, ARG_FLUSH_CACHES }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - {} - }; - - int c, r; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h46i:t:c:p:", options, NULL)) >= 0) - switch(c) { - - case 'h': - help(); - return 0; /* done */; - - case ARG_VERSION: - return version(); - - case '4': - arg_family = AF_INET; - break; - - case '6': - arg_family = AF_INET6; - break; - - case 'i': { - int ifi; - - if (parse_ifindex(optarg, &ifi) >= 0) - arg_ifindex = ifi; - else { - ifi = if_nametoindex(optarg); - if (ifi <= 0) - return log_error_errno(errno, "Unknown interface %s: %m", optarg); - - arg_ifindex = ifi; - } - - break; - } - - case 't': - if (streq(optarg, "help")) { - help_dns_types(); - return 0; - } - - r = dns_type_from_string(optarg); - if (r < 0) { - log_error("Failed to parse RR record type %s", optarg); - return r; - } - arg_type = (uint16_t) r; - assert((int) arg_type == r); - - arg_mode = MODE_RESOLVE_RECORD; - break; - - case 'c': - if (streq(optarg, "help")) { - help_dns_classes(); - return 0; - } - - r = dns_class_from_string(optarg); - if (r < 0) { - log_error("Failed to parse RR record class %s", optarg); - return r; - } - arg_class = (uint16_t) r; - assert((int) arg_class == r); - - break; - - case ARG_LEGEND: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --legend= argument"); - - arg_legend = r; - break; - - case 'p': - if (streq(optarg, "help")) { - help_protocol_types(); - return 0; - } else if (streq(optarg, "dns")) - arg_flags |= SD_RESOLVED_DNS; - else if (streq(optarg, "llmnr")) - arg_flags |= SD_RESOLVED_LLMNR; - else if (streq(optarg, "llmnr-ipv4")) - arg_flags |= SD_RESOLVED_LLMNR_IPV4; - else if (streq(optarg, "llmnr-ipv6")) - arg_flags |= SD_RESOLVED_LLMNR_IPV6; - else { - log_error("Unknown protocol specifier: %s", optarg); - return -EINVAL; - } - - break; - - case ARG_SERVICE: - arg_mode = MODE_RESOLVE_SERVICE; - break; - - case ARG_OPENPGP: - arg_mode = MODE_RESOLVE_OPENPGP; - break; - - case ARG_TLSA: - arg_mode = MODE_RESOLVE_TLSA; - arg_service_family = service_family_from_string(optarg); - if (arg_service_family < 0) { - log_error("Unknown service family \"%s\".", optarg); - return -EINVAL; - } - break; - - case ARG_RAW: - if (on_tty()) { - log_error("Refusing to write binary data to tty."); - return -ENOTTY; - } - - if (optarg == NULL || streq(optarg, "payload")) - arg_raw = RAW_PAYLOAD; - else if (streq(optarg, "packet")) - arg_raw = RAW_PACKET; - else { - log_error("Unknown --raw specifier \"%s\".", optarg); - return -EINVAL; - } - - arg_legend = false; - break; - - case ARG_CNAME: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --cname= argument."); - SET_FLAG(arg_flags, SD_RESOLVED_NO_CNAME, r == 0); - break; - - case ARG_SERVICE_ADDRESS: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --service-address= argument."); - SET_FLAG(arg_flags, SD_RESOLVED_NO_ADDRESS, r == 0); - break; - - case ARG_SERVICE_TXT: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --service-txt= argument."); - SET_FLAG(arg_flags, SD_RESOLVED_NO_TXT, r == 0); - break; - - case ARG_SEARCH: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --search argument."); - SET_FLAG(arg_flags, SD_RESOLVED_NO_SEARCH, r == 0); - break; - - case ARG_STATISTICS: - arg_mode = MODE_STATISTICS; - break; - - case ARG_RESET_STATISTICS: - arg_mode = MODE_RESET_STATISTICS; - break; - - case ARG_FLUSH_CACHES: - arg_mode = MODE_FLUSH_CACHES; - break; - - case ARG_STATUS: - arg_mode = MODE_STATUS; - break; - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (arg_type == 0 && arg_class != 0) { - log_error("--class= may only be used in conjunction with --type=."); - return -EINVAL; - } - - if (arg_type != 0 && arg_mode == MODE_RESOLVE_SERVICE) { - log_error("--service and --type= may not be combined."); - return -EINVAL; - } - - if (arg_type != 0 && arg_class == 0) - arg_class = DNS_CLASS_IN; - - if (arg_class != 0 && arg_type == 0) - arg_type = DNS_TYPE_A; - - return 1 /* work to do */; -} - -int main(int argc, char **argv) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = sd_bus_open_system(&bus); - if (r < 0) { - log_error_errno(r, "sd_bus_open_system: %m"); - goto finish; - } - - switch (arg_mode) { - - case MODE_RESOLVE_HOST: - if (optind >= argc) { - log_error("No arguments passed."); - r = -EINVAL; - goto finish; - } - - while (argv[optind]) { - int family, ifindex, k; - union in_addr_union a; - - if (startswith(argv[optind], "dns:")) - k = resolve_rfc4501(bus, argv[optind]); - else { - k = in_addr_ifindex_from_string_auto(argv[optind], &family, &a, &ifindex); - if (k >= 0) - k = resolve_address(bus, family, &a, ifindex); - else - k = resolve_host(bus, argv[optind]); - } - - if (r == 0) - r = k; - - optind++; - } - break; - - case MODE_RESOLVE_RECORD: - if (optind >= argc) { - log_error("No arguments passed."); - r = -EINVAL; - goto finish; - } - - while (argv[optind]) { - int k; - - k = resolve_record(bus, argv[optind], arg_class, arg_type, true); - if (r == 0) - r = k; - - optind++; - } - break; - - case MODE_RESOLVE_SERVICE: - if (argc < optind + 1) { - log_error("Domain specification required."); - r = -EINVAL; - goto finish; - - } else if (argc == optind + 1) - r = resolve_service(bus, NULL, NULL, argv[optind]); - else if (argc == optind + 2) - r = resolve_service(bus, NULL, argv[optind], argv[optind+1]); - else if (argc == optind + 3) - r = resolve_service(bus, argv[optind], argv[optind+1], argv[optind+2]); - else { - log_error("Too many arguments."); - r = -EINVAL; - goto finish; - } - - break; - - case MODE_RESOLVE_OPENPGP: - if (argc < optind + 1) { - log_error("E-mail address required."); - r = -EINVAL; - goto finish; - - } - - r = 0; - while (optind < argc) { - int k; - - k = resolve_openpgp(bus, argv[optind++]); - if (k < 0) - r = k; - } - break; - - case MODE_RESOLVE_TLSA: - if (argc < optind + 1) { - log_error("Domain name required."); - r = -EINVAL; - goto finish; - - } - - r = 0; - while (optind < argc) { - int k; - - k = resolve_tlsa(bus, argv[optind++]); - if (k < 0) - r = k; - } - break; - - case MODE_STATISTICS: - if (argc > optind) { - log_error("Too many arguments."); - r = -EINVAL; - goto finish; - } - - r = show_statistics(bus); - break; - - case MODE_RESET_STATISTICS: - if (argc > optind) { - log_error("Too many arguments."); - r = -EINVAL; - goto finish; - } - - r = reset_statistics(bus); - break; - - case MODE_FLUSH_CACHES: - if (argc > optind) { - log_error("Too many arguments."); - r = -EINVAL; - goto finish; - } - - r = flush_caches(bus); - break; - - case MODE_STATUS: - - if (argc > optind) { - char **ifname; - bool empty_line = false; - - r = 0; - STRV_FOREACH(ifname, argv + optind) { - int ifindex, q; - - q = parse_ifindex(argv[optind], &ifindex); - if (q < 0) { - ifindex = if_nametoindex(argv[optind]); - if (ifindex <= 0) { - log_error_errno(errno, "Failed to resolve interface name: %s", argv[optind]); - continue; - } - } - - q = status_ifindex(bus, ifindex, NULL, &empty_line); - if (q < 0 && r >= 0) - r = q; - } - } else - r = status_all(bus); - - break; - } - -finish: - pager_close(); - - return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c deleted file mode 100644 index 2ca65e6953..0000000000 --- a/src/resolve/resolved-bus.c +++ /dev/null @@ -1,1689 +0,0 @@ -/*** - 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 "bus-common-errors.h" -#include "bus-util.h" -#include "dns-domain.h" -#include "resolved-bus.h" -#include "resolved-def.h" -#include "resolved-dns-synthesize.h" -#include "resolved-link-bus.h" - -static int reply_query_state(DnsQuery *q) { - - switch (q->state) { - - case DNS_TRANSACTION_NO_SERVERS: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_NAME_SERVERS, "No appropriate name servers or networks for name found"); - - case DNS_TRANSACTION_TIMEOUT: - return sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "Query timed out"); - - case DNS_TRANSACTION_ATTEMPTS_MAX_REACHED: - return sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "All attempts to contact name servers or networks failed"); - - case DNS_TRANSACTION_INVALID_REPLY: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_INVALID_REPLY, "Received invalid reply"); - - 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"); - - case DNS_TRANSACTION_DNSSEC_FAILED: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed: %s", - dnssec_result_to_string(q->answer_dnssec_result)); - - case DNS_TRANSACTION_NO_TRUST_ANCHOR: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_TRUST_ANCHOR, "No suitable trust anchor known"); - - case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_RR_TYPE_UNSUPPORTED, "Server does not support requested resource record type"); - - case DNS_TRANSACTION_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; - - if (q->answer_rcode == DNS_RCODE_NXDOMAIN) - sd_bus_error_setf(&error, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q)); - else { - const char *rc, *n; - char p[DECIMAL_STR_MAX(q->answer_rcode)]; - - rc = dns_rcode_to_string(q->answer_rcode); - if (!rc) { - sprintf(p, "%i", q->answer_rcode); - rc = p; - } - - n = strjoina(_BUS_ERROR_DNS, rc); - sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error %s", dns_query_string(q), rc); - } - - return sd_bus_reply_method_error(q->request, &error); - } - - case DNS_TRANSACTION_NULL: - case DNS_TRANSACTION_PENDING: - case DNS_TRANSACTION_VALIDATING: - case DNS_TRANSACTION_SUCCESS: - default: - assert_not_reached("Impossible state"); - } -} - -static int append_address(sd_bus_message *reply, DnsResourceRecord *rr, int ifindex) { - int r; - - assert(reply); - assert(rr); - - r = sd_bus_message_open_container(reply, 'r', "iiay"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "i", ifindex); - if (r < 0) - return r; - - if (rr->key->type == DNS_TYPE_A) { - r = sd_bus_message_append(reply, "i", AF_INET); - if (r < 0) - return r; - - r = sd_bus_message_append_array(reply, 'y', &rr->a.in_addr, sizeof(struct in_addr)); - - } else if (rr->key->type == DNS_TYPE_AAAA) { - r = sd_bus_message_append(reply, "i", AF_INET6); - if (r < 0) - return r; - - r = sd_bus_message_append_array(reply, 'y', &rr->aaaa.in6_addr, sizeof(struct in6_addr)); - } else - return -EAFNOSUPPORT; - - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return 0; -} - -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; - _cleanup_free_ char *normalized = NULL; - DnsResourceRecord *rr; - unsigned added = 0; - int ifindex, r; - - assert(q); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } - - r = dns_query_process_cname(q); - if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); - goto finish; - } - if (r < 0) - goto finish; - if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ - return; - - r = sd_bus_message_new_method_return(q->request, &reply); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "(iiay)"); - if (r < 0) - goto finish; - - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - DnsQuestion *question; - - 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 = append_address(reply, rr, ifindex); - if (r < 0) - goto finish; - - if (!canonical) - canonical = dns_resource_record_ref(rr); - - added++; - } - - if (added <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q)); - goto finish; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - goto finish; - - /* The key names are not necessarily normalized, make sure that they are when we return them to our bus - * clients. */ - r = dns_name_normalize(dns_resource_key_name(canonical->key), &normalized); - if (r < 0) - goto finish; - - /* Return the precise spelling and uppercasing and CNAME target reported by the server */ - assert(canonical); - r = sd_bus_message_append( - reply, "st", - normalized, - SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); - if (r < 0) - goto finish; - - r = sd_bus_send(q->manager->bus, reply, NULL); - -finish: - if (r < 0) { - log_error_errno(r, "Failed to send hostname reply: %m"); - sd_bus_reply_method_errno(q->request, r, NULL); - } - - dns_query_free(q); -} - -static int check_ifindex_flags(int ifindex, uint64_t *flags, uint64_t ok, sd_bus_error *error) { - assert(flags); - - if (ifindex < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index"); - - if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_CNAME|ok)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter"); - - if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */ - *flags |= SD_RESOLVED_PROTOCOLS_ALL; - - return 0; -} - -static int parse_as_address(sd_bus_message *m, int ifindex, const char *hostname, int family, uint64_t flags) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *canonical = NULL; - union in_addr_union parsed; - int r, ff, parsed_ifindex = 0; - - /* Check if the hostname is actually already an IP address formatted as string. In that case just parse it, - * let's not attempt to look it up. */ - - r = in_addr_ifindex_from_string_auto(hostname, &ff, &parsed, &parsed_ifindex); - if (r < 0) /* not an address */ - return 0; - - if (family != AF_UNSPEC && ff != family) - return sd_bus_reply_method_errorf(m, BUS_ERROR_NO_SUCH_RR, "The specified address is not of the requested family."); - if (ifindex > 0 && parsed_ifindex > 0 && parsed_ifindex != ifindex) - return sd_bus_reply_method_errorf(m, BUS_ERROR_NO_SUCH_RR, "The specified address interface index does not match requested interface."); - - if (parsed_ifindex > 0) - ifindex = parsed_ifindex; - - r = sd_bus_message_new_method_return(m, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "(iiay)"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'r', "iiay"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "ii", ifindex, ff); - if (r < 0) - return r; - - r = sd_bus_message_append_array(reply, 'y', &parsed, FAMILY_ADDRESS_SIZE(ff)); - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - /* When an IP address is specified we just return it as canonical name, in order to avoid a DNS - * look-up. However, we reformat it to make sure it's in a truly canonical form (i.e. on IPv6 the inner - * omissions are always done the same way). */ - r = in_addr_ifindex_to_string(ff, &parsed, ifindex, &canonical); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "st", canonical, - SD_RESOLVED_FLAGS_MAKE(dns_synthesize_protocol(flags), ff, true)); - if (r < 0) - return r; - - return sd_bus_send(sd_bus_message_get_bus(m), reply, NULL); -} - -static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL; - Manager *m = userdata; - const char *hostname; - int family, ifindex; - uint64_t flags; - DnsQuery *q; - int r; - - assert(message); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(message, "isit", &ifindex, &hostname, &family, &flags); - if (r < 0) - return r; - - if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); - - r = check_ifindex_flags(ifindex, &flags, SD_RESOLVED_NO_SEARCH, error); - if (r < 0) - return r; - - r = parse_as_address(message, ifindex, hostname, family, flags); - if (r != 0) - return r; - - r = dns_name_is_valid(hostname); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", hostname); - - r = dns_question_new_address(&question_utf8, family, hostname, false); - if (r < 0) - return r; - - r = dns_question_new_address(&question_idna, family, hostname, true); - if (r < 0) - return r; - - r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags); - if (r < 0) - return r; - - q->request = sd_bus_message_ref(message); - q->request_family = family; - q->complete = bus_method_resolve_hostname_complete; - q->suppress_unroutable_family = family == AF_UNSPEC; - - r = dns_query_bus_track(q, message); - if (r < 0) - goto fail; - - r = dns_query_go(q); - if (r < 0) - goto fail; - - return 1; - -fail: - dns_query_free(q); - return r; -} - -static void bus_method_resolve_address_complete(DnsQuery *q) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - DnsQuestion *question; - DnsResourceRecord *rr; - unsigned added = 0; - int ifindex, r; - - assert(q); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } - - r = dns_query_process_cname(q); - if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); - goto finish; - } - if (r < 0) - goto finish; - if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ - return; - - r = sd_bus_message_new_method_return(q->request, &reply); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "(is)"); - if (r < 0) - goto finish; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - _cleanup_free_ char *normalized = NULL; - - r = dns_question_matches_rr(question, rr, NULL); - if (r < 0) - goto finish; - if (r == 0) - continue; - - r = dns_name_normalize(rr->ptr.name, &normalized); - if (r < 0) - goto finish; - - r = sd_bus_message_append(reply, "(is)", ifindex, normalized); - if (r < 0) - goto finish; - - added++; - } - - if (added <= 0) { - _cleanup_free_ char *ip = NULL; - - (void) in_addr_to_string(q->request_family, &q->request_address, &ip); - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, - "Address '%s' does not have any RR of requested type", strnull(ip)); - goto finish; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - goto finish; - - r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); - if (r < 0) - goto finish; - - r = sd_bus_send(q->manager->bus, reply, NULL); - -finish: - if (r < 0) { - log_error_errno(r, "Failed to send address reply: %m"); - sd_bus_reply_method_errno(q->request, r, NULL); - } - - dns_query_free(q); -} - -static int bus_method_resolve_address(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; - Manager *m = userdata; - int family, ifindex; - uint64_t flags; - const void *d; - DnsQuery *q; - size_t sz; - int r; - - assert(message); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(message, "ii", &ifindex, &family); - if (r < 0) - return r; - - if (!IN_SET(family, AF_INET, AF_INET6)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); - - r = sd_bus_message_read_array(message, 'y', &d, &sz); - if (r < 0) - return r; - - if (sz != FAMILY_ADDRESS_SIZE(family)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid address size"); - - r = sd_bus_message_read(message, "t", &flags); - if (r < 0) - return r; - - r = check_ifindex_flags(ifindex, &flags, 0, error); - if (r < 0) - return r; - - r = dns_question_new_reverse(&question, family, d); - if (r < 0) - return r; - - r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); - if (r < 0) - return r; - - q->request = sd_bus_message_ref(message); - q->request_family = family; - memcpy(&q->request_address, d, sz); - q->complete = bus_method_resolve_address_complete; - - r = dns_query_bus_track(q, message); - if (r < 0) - goto fail; - - r = dns_query_go(q); - if (r < 0) - goto fail; - - return 1; - -fail: - dns_query_free(q); - return r; -} - -static int bus_message_append_rr(sd_bus_message *m, DnsResourceRecord *rr, int ifindex) { - int r; - - assert(m); - assert(rr); - - r = sd_bus_message_open_container(m, 'r', "iqqay"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "iqq", - ifindex, - rr->key->class, - rr->key->type); - if (r < 0) - return r; - - r = dns_resource_record_to_wire_format(rr, false); - if (r < 0) - return r; - - r = sd_bus_message_append_array(m, 'y', rr->wire_format, rr->wire_format_size); - if (r < 0) - return r; - - return sd_bus_message_close_container(m); -} - -static void bus_method_resolve_record_complete(DnsQuery *q) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - DnsResourceRecord *rr; - DnsQuestion *question; - unsigned added = 0; - int ifindex; - int r; - - assert(q); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } - - r = dns_query_process_cname(q); - if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); - goto finish; - } - if (r < 0) - goto finish; - if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ - return; - - r = sd_bus_message_new_method_return(q->request, &reply); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "(iqqay)"); - if (r < 0) - goto finish; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - r = dns_question_matches_rr(question, rr, NULL); - if (r < 0) - goto finish; - if (r == 0) - continue; - - r = bus_message_append_rr(reply, rr, ifindex); - if (r < 0) - goto finish; - - added++; - } - - if (added <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Name '%s' does not have any RR of the requested type", dns_query_string(q)); - goto finish; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - goto finish; - - r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); - if (r < 0) - goto finish; - - r = sd_bus_send(q->manager->bus, reply, NULL); - -finish: - if (r < 0) { - log_error_errno(r, "Failed to send record reply: %m"); - sd_bus_reply_method_errno(q->request, r, NULL); - } - - dns_query_free(q); -} - -static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; - Manager *m = userdata; - uint16_t class, type; - const char *name; - int r, ifindex; - uint64_t flags; - DnsQuery *q; - - assert(message); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(message, "isqqt", &ifindex, &name, &class, &type, &flags); - if (r < 0) - return r; - - r = dns_name_is_valid(name); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid name '%s'", name); - - if (!dns_type_is_valid_query(type)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified resource record type %" PRIu16 " may not be used in a query.", type); - if (dns_type_is_zone_transer(type)) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Zone transfers not permitted via this programming interface."); - if (dns_type_is_obsolete(type)) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Specified DNS resource record type %" PRIu16 " is obsolete.", type); - - r = check_ifindex_flags(ifindex, &flags, 0, error); - if (r < 0) - return r; - - question = dns_question_new(1); - if (!question) - return -ENOMEM; - - key = dns_resource_key_new(class, type, name); - if (!key) - return -ENOMEM; - - r = dns_question_add(question, key); - if (r < 0) - return r; - - r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); - if (r < 0) - return r; - - /* Let's request that the TTL is fixed up for locally cached entries, after all we return it in the wire format - * blob */ - q->clamp_ttl = true; - - q->request = sd_bus_message_ref(message); - q->complete = bus_method_resolve_record_complete; - - r = dns_query_bus_track(q, message); - if (r < 0) - goto fail; - - r = dns_query_go(q); - if (r < 0) - goto fail; - - return 1; - -fail: - dns_query_free(q); - return r; -} - -static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; - _cleanup_free_ char *normalized = NULL; - DnsQuery *aux; - int r; - - assert(q); - assert(reply); - assert(rr); - assert(rr->key); - - if (rr->key->type != DNS_TYPE_SRV) - return 0; - - if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { - /* First, let's see if we could find an appropriate A or AAAA - * record for the SRV record */ - LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { - DnsResourceRecord *zz; - DnsQuestion *question; - - if (aux->state != DNS_TRANSACTION_SUCCESS) - continue; - if (aux->auxiliary_result != 0) - continue; - - question = dns_query_question_for_protocol(aux, aux->answer_protocol); - - r = dns_name_equal(dns_question_first_name(question), rr->srv.name); - if (r < 0) - return r; - if (r == 0) - continue; - - DNS_ANSWER_FOREACH(zz, aux->answer) { - - r = dns_question_matches_rr(question, zz, NULL); - if (r < 0) - return r; - if (r == 0) - continue; - - canonical = dns_resource_record_ref(zz); - break; - } - - if (canonical) - break; - } - - /* Is there are successful A/AAAA lookup for this SRV RR? If not, don't add it */ - if (!canonical) - return 0; - } - - r = sd_bus_message_open_container(reply, 'r', "qqqsa(iiay)s"); - if (r < 0) - return r; - - r = dns_name_normalize(rr->srv.name, &normalized); - if (r < 0) - return r; - - r = sd_bus_message_append( - reply, - "qqqs", - rr->srv.priority, rr->srv.weight, rr->srv.port, normalized); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "(iiay)"); - if (r < 0) - return r; - - if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { - LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { - DnsResourceRecord *zz; - DnsQuestion *question; - int ifindex; - - if (aux->state != DNS_TRANSACTION_SUCCESS) - continue; - if (aux->auxiliary_result != 0) - continue; - - question = dns_query_question_for_protocol(aux, aux->answer_protocol); - - r = dns_name_equal(dns_question_first_name(question), rr->srv.name); - if (r < 0) - return r; - if (r == 0) - continue; - - DNS_ANSWER_FOREACH_IFINDEX(zz, ifindex, aux->answer) { - - r = dns_question_matches_rr(question, zz, NULL); - if (r < 0) - return r; - if (r == 0) - continue; - - r = append_address(reply, zz, ifindex); - if (r < 0) - return r; - } - } - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - if (canonical) { - normalized = mfree(normalized); - - r = dns_name_normalize(dns_resource_key_name(canonical->key), &normalized); - if (r < 0) - return r; - } - - /* Note that above we appended the hostname as encoded in the - * SRV, and here the canonical hostname this maps to. */ - r = sd_bus_message_append(reply, "s", normalized); - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return 1; -} - -static int append_txt(sd_bus_message *reply, DnsResourceRecord *rr) { - DnsTxtItem *i; - int r; - - assert(reply); - assert(rr); - assert(rr->key); - - if (rr->key->type != DNS_TYPE_TXT) - return 0; - - LIST_FOREACH(items, i, rr->txt.items) { - - if (i->length <= 0) - continue; - - r = sd_bus_message_append_array(reply, 'y', i->data, i->length); - if (r < 0) - return r; - } - - return 1; -} - -static void resolve_service_all_complete(DnsQuery *q) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL; - DnsQuestion *question; - DnsResourceRecord *rr; - unsigned added = 0; - DnsQuery *aux; - int r; - - assert(q); - - if (q->block_all_complete > 0) - return; - - if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { - DnsQuery *bad = NULL; - bool have_success = false; - - LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { - - switch (aux->state) { - - case DNS_TRANSACTION_PENDING: - /* If an auxiliary query is still pending, let's wait */ - return; - - case DNS_TRANSACTION_SUCCESS: - if (aux->auxiliary_result == 0) - have_success = true; - else - bad = aux; - break; - - default: - bad = aux; - break; - } - } - - if (!have_success) { - /* We can only return one error, hence pick the last error we encountered */ - - assert(bad); - - if (bad->state == DNS_TRANSACTION_SUCCESS) { - assert(bad->auxiliary_result != 0); - - if (bad->auxiliary_result == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(bad)); - goto finish; - } - - r = bad->auxiliary_result; - goto finish; - } - - r = reply_query_state(bad); - goto finish; - } - } - - r = sd_bus_message_new_method_return(q->request, &reply); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "(qqqsa(iiay)s)"); - if (r < 0) - goto finish; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - DNS_ANSWER_FOREACH(rr, q->answer) { - r = dns_question_matches_rr(question, rr, NULL); - if (r < 0) - goto finish; - if (r == 0) - continue; - - r = append_srv(q, reply, rr); - if (r < 0) - goto finish; - if (r == 0) /* not an SRV record */ - continue; - - if (!canonical) - canonical = dns_resource_record_ref(rr); - - added++; - } - - if (added <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q)); - goto finish; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "ay"); - if (r < 0) - goto finish; - - DNS_ANSWER_FOREACH(rr, q->answer) { - r = dns_question_matches_rr(question, rr, NULL); - if (r < 0) - goto finish; - if (r == 0) - continue; - - r = append_txt(reply, rr); - if (r < 0) - goto finish; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - goto finish; - - assert(canonical); - r = dns_service_split(dns_resource_key_name(canonical->key), &name, &type, &domain); - if (r < 0) - goto finish; - - r = sd_bus_message_append( - reply, - "ssst", - name, type, domain, - SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); - if (r < 0) - goto finish; - - r = sd_bus_send(q->manager->bus, reply, NULL); - -finish: - if (r < 0) { - log_error_errno(r, "Failed to send service reply: %m"); - sd_bus_reply_method_errno(q->request, r, NULL); - } - - dns_query_free(q); -} - -static void resolve_service_hostname_complete(DnsQuery *q) { - int r; - - assert(q); - assert(q->auxiliary_for); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - resolve_service_all_complete(q->auxiliary_for); - return; - } - - r = dns_query_process_cname(q); - if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ - return; - - /* This auxiliary lookup is finished or failed, let's see if all are finished now. */ - q->auxiliary_result = r; - resolve_service_all_complete(q->auxiliary_for); -} - -static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifindex) { - _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; - DnsQuery *aux; - int r; - - assert(q); - assert(rr); - assert(rr->key); - assert(rr->key->type == DNS_TYPE_SRV); - - /* OK, we found an SRV record for the service. Let's resolve - * the hostname included in it */ - - r = dns_question_new_address(&question, q->request_family, rr->srv.name, false); - if (r < 0) - return r; - - r = dns_query_new(q->manager, &aux, question, question, ifindex, q->flags|SD_RESOLVED_NO_SEARCH); - if (r < 0) - return r; - - aux->request_family = q->request_family; - aux->complete = resolve_service_hostname_complete; - - r = dns_query_make_auxiliary(aux, q); - if (r == -EAGAIN) { - /* Too many auxiliary lookups? If so, don't complain, - * let's just not add this one, we already have more - * than enough */ - - dns_query_free(aux); - return 0; - } - if (r < 0) - goto fail; - - /* Note that auxiliary queries do not track the original bus - * client, only the primary request does that. */ - - r = dns_query_go(aux); - if (r < 0) - goto fail; - - return 1; - -fail: - dns_query_free(aux); - return r; -} - -static void bus_method_resolve_service_complete(DnsQuery *q) { - bool has_root_domain = false; - DnsResourceRecord *rr; - DnsQuestion *question; - unsigned found = 0; - int ifindex, r; - - assert(q); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } - - r = dns_query_process_cname(q); - if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); - goto finish; - } - if (r < 0) - goto finish; - if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ - return; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - r = dns_question_matches_rr(question, rr, NULL); - if (r < 0) - goto finish; - if (r == 0) - continue; - - if (rr->key->type != DNS_TYPE_SRV) - continue; - - if (dns_name_is_root(rr->srv.name)) { - has_root_domain = true; - continue; - } - - if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { - q->block_all_complete++; - r = resolve_service_hostname(q, rr, ifindex); - q->block_all_complete--; - - if (r < 0) - goto finish; - } - - found++; - } - - if (has_root_domain && found <= 0) { - /* If there's exactly one SRV RR and it uses - * the root domain as host name, then the - * service is explicitly not offered on the - * domain. Report this as a recognizable - * error. See RFC 2782, Section "Usage - * Rules". */ - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_SERVICE, "'%s' does not provide the requested service", dns_query_string(q)); - goto finish; - } - - if (found <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q)); - goto finish; - } - - /* Maybe we are already finished? check now... */ - resolve_service_all_complete(q); - return; - -finish: - if (r < 0) { - log_error_errno(r, "Failed to send service reply: %m"); - sd_bus_reply_method_errno(q->request, r, NULL); - } - - dns_query_free(q); -} - -static int bus_method_resolve_service(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL; - const char *name, *type, *domain; - Manager *m = userdata; - int family, ifindex; - uint64_t flags; - DnsQuery *q; - int r; - - assert(message); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(message, "isssit", &ifindex, &name, &type, &domain, &family, &flags); - if (r < 0) - return r; - - if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); - - if (isempty(name)) - name = NULL; - else if (!dns_service_name_is_valid(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid service name '%s'", name); - - if (isempty(type)) - type = NULL; - else if (!dns_srv_type_is_valid(type)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid SRV service type '%s'", type); - - r = dns_name_is_valid(domain); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid domain '%s'", domain); - - if (name && !type) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Service name cannot be specified without service type."); - - r = check_ifindex_flags(ifindex, &flags, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS, error); - if (r < 0) - return r; - - r = dns_question_new_service(&question_utf8, name, type, domain, !(flags & SD_RESOLVED_NO_TXT), false); - if (r < 0) - return r; - - r = dns_question_new_service(&question_idna, name, type, domain, !(flags & SD_RESOLVED_NO_TXT), true); - if (r < 0) - return r; - - r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags|SD_RESOLVED_NO_SEARCH); - if (r < 0) - return r; - - q->request = sd_bus_message_ref(message); - q->request_family = family; - q->complete = bus_method_resolve_service_complete; - - r = dns_query_bus_track(q, message); - if (r < 0) - goto fail; - - r = dns_query_go(q); - if (r < 0) - goto fail; - - return 1; - -fail: - dns_query_free(q); - return r; -} - -int bus_dns_server_append(sd_bus_message *reply, DnsServer *s, bool with_ifindex) { - int r; - - assert(reply); - assert(s); - - r = sd_bus_message_open_container(reply, 'r', with_ifindex ? "iiay" : "iay"); - if (r < 0) - return r; - - if (with_ifindex) { - r = sd_bus_message_append(reply, "i", dns_server_ifindex(s)); - if (r < 0) - return r; - } - - r = sd_bus_message_append(reply, "i", s->family); - if (r < 0) - return r; - - r = sd_bus_message_append_array(reply, 'y', &s->address, FAMILY_ADDRESS_SIZE(s->family)); - if (r < 0) - return r; - - return sd_bus_message_close_container(reply); -} - -static int bus_property_get_dns_servers( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Manager *m = userdata; - unsigned c = 0; - DnsServer *s; - Iterator i; - Link *l; - int r; - - assert(reply); - assert(m); - - r = sd_bus_message_open_container(reply, 'a', "(iiay)"); - if (r < 0) - return r; - - LIST_FOREACH(servers, s, m->dns_servers) { - r = bus_dns_server_append(reply, s, true); - if (r < 0) - return r; - - c++; - } - - HASHMAP_FOREACH(l, m->links, i) { - LIST_FOREACH(servers, s, l->dns_servers) { - r = bus_dns_server_append(reply, s, true); - if (r < 0) - return r; - c++; - } - } - - if (c == 0) { - LIST_FOREACH(servers, s, m->fallback_dns_servers) { - r = bus_dns_server_append(reply, s, true); - if (r < 0) - return r; - } - } - - return sd_bus_message_close_container(reply); -} - -static int bus_property_get_domains( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Manager *m = userdata; - DnsSearchDomain *d; - Iterator i; - Link *l; - int r; - - assert(reply); - assert(m); - - r = sd_bus_message_open_container(reply, 'a', "(isb)"); - if (r < 0) - return r; - - LIST_FOREACH(domains, d, m->search_domains) { - r = sd_bus_message_append(reply, "(isb)", 0, d->name, d->route_only); - if (r < 0) - return r; - } - - HASHMAP_FOREACH(l, m->links, i) { - LIST_FOREACH(domains, d, l->search_domains) { - r = sd_bus_message_append(reply, "(isb)", l->ifindex, d->name, d->route_only); - if (r < 0) - return r; - } - } - - return sd_bus_message_close_container(reply); -} - -static int bus_property_get_transaction_statistics( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Manager *m = userdata; - - assert(reply); - assert(m); - - return sd_bus_message_append(reply, "(tt)", - (uint64_t) hashmap_size(m->dns_transactions), - (uint64_t) m->n_transactions_total); -} - -static int bus_property_get_cache_statistics( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - uint64_t size = 0, hit = 0, miss = 0; - Manager *m = userdata; - DnsScope *s; - - assert(reply); - assert(m); - - LIST_FOREACH(scopes, s, m->dns_scopes) { - size += dns_cache_size(&s->cache); - hit += s->cache.n_hit; - miss += s->cache.n_miss; - } - - return sd_bus_message_append(reply, "(ttt)", size, hit, miss); -} - -static int bus_property_get_dnssec_statistics( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Manager *m = userdata; - - assert(reply); - assert(m); - - return sd_bus_message_append(reply, "(tttt)", - (uint64_t) m->n_dnssec_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( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Manager *m = userdata; - - assert(reply); - assert(m); - - return sd_bus_message_append(reply, "b", manager_dnssec_supported(m)); -} - -static int bus_property_get_ntas( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Manager *m = userdata; - const char *domain; - Iterator i; - int r; - - assert(reply); - assert(m); - - r = sd_bus_message_open_container(reply, 'a', "s"); - if (r < 0) - return r; - - SET_FOREACH(domain, m->trust_anchor.negative_by_name, i) { - r = sd_bus_message_append(reply, "s", domain); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int bus_method_reset_statistics(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - DnsScope *s; - - assert(message); - assert(m); - - LIST_FOREACH(scopes, s, m->dns_scopes) - s->cache.n_hit = s->cache.n_miss = 0; - - m->n_transactions_total = 0; - zero(m->n_dnssec_verdict); - - return sd_bus_reply_method_return(message, NULL); -} - -static int get_any_link(Manager *m, int ifindex, Link **ret, sd_bus_error *error) { - Link *l; - - assert(m); - assert(ret); - - if (ifindex <= 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index"); - - l = hashmap_get(m->links, INT_TO_PTR(ifindex)); - if (!l) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_LINK, "Link %i not known", ifindex); - - *ret = l; - return 0; -} - -static int call_link_method(Manager *m, sd_bus_message *message, sd_bus_message_handler_t handler, sd_bus_error *error) { - int ifindex, r; - Link *l; - - assert(m); - assert(message); - assert(handler); - - assert_cc(sizeof(int) == sizeof(int32_t)); - r = sd_bus_message_read(message, "i", &ifindex); - if (r < 0) - return r; - - r = get_any_link(m, ifindex, &l, error); - if (r < 0) - return r; - - return handler(message, l, error); -} - -static int bus_method_set_link_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return call_link_method(userdata, message, bus_link_method_set_dns_servers, error); -} - -static int bus_method_set_link_domains(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return call_link_method(userdata, message, bus_link_method_set_domains, error); -} - -static int bus_method_set_link_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return call_link_method(userdata, message, bus_link_method_set_llmnr, error); -} - -static int bus_method_set_link_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return call_link_method(userdata, message, bus_link_method_set_mdns, error); -} - -static int bus_method_set_link_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return call_link_method(userdata, message, bus_link_method_set_dnssec, error); -} - -static int bus_method_set_link_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return call_link_method(userdata, message, bus_link_method_set_dnssec_negative_trust_anchors, error); -} - -static int bus_method_revert_link(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return call_link_method(userdata, message, bus_link_method_revert, error); -} - -static int bus_method_get_link(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *p = NULL; - Manager *m = userdata; - int r, ifindex; - Link *l; - - assert(message); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - r = sd_bus_message_read(message, "i", &ifindex); - if (r < 0) - return r; - - r = get_any_link(m, ifindex, &l, error); - if (r < 0) - return r; - - p = link_bus_path(l); - if (!p) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", p); -} - -static int bus_method_flush_caches(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - - assert(message); - assert(m); - - manager_flush_caches(m); - - return sd_bus_reply_method_return(message, NULL); -} - -static const sd_bus_vtable resolve_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("LLMNRHostname", "s", NULL, offsetof(Manager, llmnr_hostname), 0), - SD_BUS_PROPERTY("DNS", "a(iiay)", bus_property_get_dns_servers, 0, 0), - SD_BUS_PROPERTY("Domains", "a(isb)", bus_property_get_domains, 0, 0), - SD_BUS_PROPERTY("TransactionStatistics", "(tt)", bus_property_get_transaction_statistics, 0, 0), - SD_BUS_PROPERTY("CacheStatistics", "(ttt)", bus_property_get_cache_statistics, 0, 0), - SD_BUS_PROPERTY("DNSSECStatistics", "(tttt)", bus_property_get_dnssec_statistics, 0, 0), - SD_BUS_PROPERTY("DNSSECSupported", "b", bus_property_get_dnssec_supported, 0, 0), - SD_BUS_PROPERTY("DNSSECNegativeTrustAnchors", "as", bus_property_get_ntas, 0, 0), - - SD_BUS_METHOD("ResolveHostname", "isit", "a(iiay)st", bus_method_resolve_hostname, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ResolveAddress", "iiayt", "a(is)t", bus_method_resolve_address, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ResolveRecord", "isqqt", "a(iqqay)t", bus_method_resolve_record, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ResolveService", "isssit", "a(qqqsa(iiay)s)aayssst", bus_method_resolve_service, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ResetStatistics", NULL, NULL, bus_method_reset_statistics, 0), - SD_BUS_METHOD("FlushCaches", NULL, NULL, bus_method_flush_caches, 0), - SD_BUS_METHOD("GetLink", "i", "o", bus_method_get_link, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetLinkDNS", "ia(iay)", NULL, bus_method_set_link_dns_servers, 0), - SD_BUS_METHOD("SetLinkDomains", "ia(sb)", NULL, bus_method_set_link_domains, 0), - SD_BUS_METHOD("SetLinkLLMNR", "is", NULL, bus_method_set_link_llmnr, 0), - SD_BUS_METHOD("SetLinkMulticastDNS", "is", NULL, bus_method_set_link_mdns, 0), - SD_BUS_METHOD("SetLinkDNSSEC", "is", NULL, bus_method_set_link_dnssec, 0), - SD_BUS_METHOD("SetLinkDNSSECNegativeTrustAnchors", "ias", NULL, bus_method_set_link_dnssec_negative_trust_anchors, 0), - SD_BUS_METHOD("RevertLink", "i", NULL, bus_method_revert_link, 0), - - SD_BUS_VTABLE_END, -}; - -static int on_bus_retry(sd_event_source *s, usec_t usec, void *userdata) { - Manager *m = userdata; - - assert(s); - assert(m); - - m->bus_retry_event_source = sd_event_source_unref(m->bus_retry_event_source); - - manager_connect_bus(m); - return 0; -} - -static int match_prepare_for_sleep(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) { - Manager *m = userdata; - int b, r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) { - log_debug_errno(r, "Failed to parse PrepareForSleep signal: %m"); - return 0; - } - - if (b) - return 0; - - log_debug("Coming back from suspend, verifying all RRs..."); - - manager_verify_all(m); - return 0; -} - -int manager_connect_bus(Manager *m) { - int r; - - assert(m); - - if (m->bus) - return 0; - - r = sd_bus_default_system(&m->bus); - if (r < 0) { - /* We failed to connect? Yuck, we must be in early - * boot. Let's try in 5s again. As soon as we have - * kdbus we can stop doing this... */ - - log_debug_errno(r, "Failed to connect to bus, trying again in 5s: %m"); - - r = sd_event_add_time(m->event, &m->bus_retry_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 5*USEC_PER_SEC, 0, on_bus_retry, m); - if (r < 0) - return log_error_errno(r, "Failed to install bus reconnect time event: %m"); - - (void) sd_event_source_set_description(m->bus_retry_event_source, "bus-retry"); - return 0; - } - - r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/resolve1", "org.freedesktop.resolve1.Manager", resolve_vtable, m); - if (r < 0) - return log_error_errno(r, "Failed to register object: %m"); - - r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/resolve1/link", "org.freedesktop.resolve1.Link", link_vtable, link_object_find, m); - if (r < 0) - return log_error_errno(r, "Failed to register link objects: %m"); - - r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/resolve1/link", link_node_enumerator, m); - if (r < 0) - return log_error_errno(r, "Failed to register link enumerator: %m"); - - r = sd_bus_request_name(m->bus, "org.freedesktop.resolve1", 0); - if (r < 0) - return log_error_errno(r, "Failed to register name: %m"); - - r = sd_bus_attach_event(m->bus, m->event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); - - r = sd_bus_add_match(m->bus, &m->prepare_for_sleep_slot, - "type='signal'," - "sender='org.freedesktop.login1'," - "interface='org.freedesktop.login1.Manager'," - "member='PrepareForSleep'," - "path='/org/freedesktop/login1'", - match_prepare_for_sleep, - m); - if (r < 0) - log_error_errno(r, "Failed to add match for PrepareForSleep: %m"); - - return 0; -} diff --git a/src/resolve/resolved-bus.h b/src/resolve/resolved-bus.h deleted file mode 100644 index f49e1337d2..0000000000 --- a/src/resolve/resolved-bus.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include "resolved-manager.h" - -int manager_connect_bus(Manager *m); -int bus_dns_server_append(sd_bus_message *reply, DnsServer *s, bool with_ifindex); diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c deleted file mode 100644 index abf3263178..0000000000 --- a/src/resolve/resolved-conf.c +++ /dev/null @@ -1,251 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen <teg@jklm.no> - - 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 "conf-parser.h" -#include "def.h" -#include "extract-word.h" -#include "parse-util.h" -#include "resolved-conf.h" -#include "string-table.h" -#include "string-util.h" - -DEFINE_CONFIG_PARSE_ENUM(config_parse_dns_stub_listener_mode, dns_stub_listener_mode, DnsStubListenerMode, "Failed to parse DNS stub listener mode setting"); - -static const char* const dns_stub_listener_mode_table[_DNS_STUB_LISTENER_MODE_MAX] = { - [DNS_STUB_LISTENER_NO] = "no", - [DNS_STUB_LISTENER_UDP] = "udp", - [DNS_STUB_LISTENER_TCP] = "tcp", - [DNS_STUB_LISTENER_YES] = "yes", -}; -DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dns_stub_listener_mode, DnsStubListenerMode, DNS_STUB_LISTENER_YES); - -int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word) { - union in_addr_union address; - int family, r, ifindex = 0; - DnsServer *s; - - assert(m); - assert(word); - - r = in_addr_ifindex_from_string_auto(word, &family, &address, &ifindex); - if (r < 0) - return r; - - /* Silently filter out 0.0.0.0 and 127.0.0.53 (our own stub DNS listener) */ - if (!dns_server_address_valid(family, &address)) - return 0; - - /* Filter out duplicates */ - s = dns_server_find(manager_get_first_dns_server(m, type), family, &address, ifindex); - if (s) { - /* - * Drop the marker. This is used to find the servers - * that ceased to exist, see - * manager_mark_dns_servers() and - * manager_flush_marked_dns_servers(). - */ - dns_server_move_back_and_unmark(s); - return 0; - } - - return dns_server_new(m, NULL, type, NULL, family, &address, ifindex); -} - -int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string) { - int r; - - assert(m); - assert(string); - - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&string, &word, NULL, 0); - if (r < 0) - return r; - if (r == 0) - break; - - r = manager_add_dns_server_by_string(m, type, word); - if (r < 0) - log_warning_errno(r, "Failed to add DNS server address '%s', ignoring: %m", word); - } - - return 0; -} - -int manager_add_search_domain_by_string(Manager *m, const char *domain) { - DnsSearchDomain *d; - bool route_only; - int r; - - assert(m); - assert(domain); - - route_only = *domain == '~'; - if (route_only) - domain++; - - if (dns_name_is_root(domain) || streq(domain, "*")) { - route_only = true; - domain = "."; - } - - r = dns_search_domain_find(m->search_domains, domain, &d); - if (r < 0) - return r; - if (r > 0) - dns_search_domain_move_back_and_unmark(d); - else { - r = dns_search_domain_new(m, &d, DNS_SEARCH_DOMAIN_SYSTEM, NULL, domain); - if (r < 0) - return r; - } - - d->route_only = route_only; - return 0; -} - -int manager_parse_search_domains_and_warn(Manager *m, const char *string) { - int r; - - assert(m); - assert(string); - - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&string, &word, NULL, EXTRACT_QUOTES); - if (r < 0) - return r; - if (r == 0) - break; - - r = manager_add_search_domain_by_string(m, word); - if (r < 0) - log_warning_errno(r, "Failed to add search domain '%s', ignoring: %m", word); - } - - return 0; -} - -int config_parse_dns_servers( - const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - Manager *m = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(m); - - if (isempty(rvalue)) - /* Empty assignment means clear the list */ - dns_server_unlink_all(manager_get_first_dns_server(m, ltype)); - else { - /* Otherwise, add to the list */ - r = manager_parse_dns_server_string_and_warn(m, ltype, rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse DNS server string '%s'. Ignoring.", rvalue); - return 0; - } - } - - /* If we have a manual setting, then we stop reading - * /etc/resolv.conf */ - if (ltype == DNS_SERVER_SYSTEM) - m->read_resolv_conf = false; - if (ltype == DNS_SERVER_FALLBACK) - m->need_builtin_fallbacks = false; - - return 0; -} - -int config_parse_search_domains( - const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - Manager *m = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(m); - - if (isempty(rvalue)) - /* Empty assignment means clear the list */ - dns_search_domain_unlink_all(m->search_domains); - else { - /* Otherwise, add to the list */ - r = manager_parse_search_domains_and_warn(m, rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse search domains string '%s'. Ignoring.", rvalue); - return 0; - } - } - - /* If we have a manual setting, then we stop reading - * /etc/resolv.conf */ - m->read_resolv_conf = false; - - return 0; -} - -int manager_parse_config_file(Manager *m) { - int r; - - assert(m); - - r = config_parse_many_nulstr(PKGSYSCONFDIR "/resolved.conf", - CONF_PATHS_NULSTR("systemd/resolved.conf.d"), - "Resolve\0", - config_item_perf_lookup, resolved_gperf_lookup, - false, m); - if (r < 0) - return r; - - if (m->need_builtin_fallbacks) { - r = manager_parse_dns_server_string_and_warn(m, DNS_SERVER_FALLBACK, DNS_SERVERS); - if (r < 0) - return r; - } - - return 0; - -} diff --git a/src/resolve/resolved-conf.h b/src/resolve/resolved-conf.h deleted file mode 100644 index 8184d6cadf..0000000000 --- a/src/resolve/resolved-conf.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen <teg@jklm.no> - - 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/>. -***/ - -typedef enum DnsStubListenerMode DnsStubListenerMode; - -enum DnsStubListenerMode { - DNS_STUB_LISTENER_NO, - DNS_STUB_LISTENER_UDP, - DNS_STUB_LISTENER_TCP, - DNS_STUB_LISTENER_YES, - _DNS_STUB_LISTENER_MODE_MAX, - _DNS_STUB_LISTENER_MODE_INVALID = -1 -}; - -#include "resolved-manager.h" -#include "resolved-dns-server.h" - -int manager_parse_config_file(Manager *m); - -int manager_add_search_domain_by_string(Manager *m, const char *domain); -int manager_parse_search_domains_and_warn(Manager *m, const char *string); - -int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word); -int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string); - -const struct ConfigPerfItem* resolved_gperf_lookup(const char *key, GPERF_LEN_TYPE length); - -int config_parse_dns_servers(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_search_domains(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_dns_stub_listener_mode(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); - -const char* dns_stub_listener_mode_to_string(DnsStubListenerMode p) _const_; -DnsStubListenerMode dns_stub_listener_mode_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-def.h b/src/resolve/resolved-def.h deleted file mode 100644 index c4c1915b18..0000000000 --- a/src/resolve/resolved-def.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include <inttypes.h> - -#define SD_RESOLVED_DNS (UINT64_C(1) << 0) -#define SD_RESOLVED_LLMNR_IPV4 (UINT64_C(1) << 1) -#define SD_RESOLVED_LLMNR_IPV6 (UINT64_C(1) << 2) -#define SD_RESOLVED_MDNS_IPV4 (UINT64_C(1) << 3) -#define SD_RESOLVED_MDNS_IPV6 (UINT64_C(1) << 4) -#define SD_RESOLVED_NO_CNAME (UINT64_C(1) << 5) -#define SD_RESOLVED_NO_TXT (UINT64_C(1) << 6) -#define SD_RESOLVED_NO_ADDRESS (UINT64_C(1) << 7) -#define SD_RESOLVED_NO_SEARCH (UINT64_C(1) << 8) -#define SD_RESOLVED_AUTHENTICATED (UINT64_C(1) << 9) - -#define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6) -#define SD_RESOLVED_MDNS (SD_RESOLVED_MDNS_IPV4|SD_RESOLVED_MDNS_IPV6) - -#define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_MDNS|SD_RESOLVED_LLMNR|SD_RESOLVED_DNS) diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c deleted file mode 100644 index ab85754bf7..0000000000 --- a/src/resolve/resolved-dns-answer.c +++ /dev/null @@ -1,858 +0,0 @@ -/*** - 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 "dns-domain.h" -#include "resolved-dns-answer.h" -#include "resolved-dns-dnssec.h" -#include "string-util.h" - -DnsAnswer *dns_answer_new(unsigned n) { - DnsAnswer *a; - - a = malloc0(offsetof(DnsAnswer, items) + sizeof(DnsAnswerItem) * n); - if (!a) - return NULL; - - a->n_ref = 1; - a->n_allocated = n; - - return a; -} - -DnsAnswer *dns_answer_ref(DnsAnswer *a) { - if (!a) - return NULL; - - assert(a->n_ref > 0); - a->n_ref++; - return a; -} - -static void dns_answer_flush(DnsAnswer *a) { - DnsResourceRecord *rr; - - if (!a) - return; - - DNS_ANSWER_FOREACH(rr, a) - dns_resource_record_unref(rr); - - a->n_rrs = 0; -} - -DnsAnswer *dns_answer_unref(DnsAnswer *a) { - if (!a) - return NULL; - - assert(a->n_ref > 0); - - if (a->n_ref == 1) { - dns_answer_flush(a); - free(a); - } else - a->n_ref--; - - return NULL; -} - -static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { - assert(rr); - - if (!a) - return -ENOSPC; - - if (a->n_rrs >= a->n_allocated) - return -ENOSPC; - - a->items[a->n_rrs++] = (DnsAnswerItem) { - .rr = dns_resource_record_ref(rr), - .ifindex = ifindex, - .flags = flags, - }; - - return 1; -} - -static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) { - DnsResourceRecord *rr; - DnsAnswerFlags flags; - int ifindex, r; - - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, source) { - r = dns_answer_add_raw(a, rr, ifindex, flags); - if (r < 0) - return r; - } - - return 0; -} - -int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { - unsigned i; - int r; - - assert(rr); - - if (!a) - return -ENOSPC; - if (a->n_ref > 1) - return -EBUSY; - - for (i = 0; i < a->n_rrs; i++) { - if (a->items[i].ifindex != ifindex) - continue; - - r = dns_resource_record_equal(a->items[i].rr, rr); - if (r < 0) - return r; - if (r > 0) { - /* Don't mix contradicting TTLs (see below) */ - if ((rr->ttl == 0) != (a->items[i].rr->ttl == 0)) - return -EINVAL; - - /* Entry already exists, keep the entry with - * the higher RR. */ - if (rr->ttl > a->items[i].rr->ttl) { - dns_resource_record_ref(rr); - dns_resource_record_unref(a->items[i].rr); - a->items[i].rr = rr; - } - - a->items[i].flags |= flags; - return 0; - } - - r = dns_resource_key_equal(a->items[i].rr->key, rr->key); - if (r < 0) - return r; - if (r > 0) { - /* There's already an RR of the same RRset in - * place! Let's see if the TTLs more or less - * match. We don't really care if they match - * precisely, but we do care whether one is 0 - * and the other is not. See RFC 2181, Section - * 5.2.*/ - - if ((rr->ttl == 0) != (a->items[i].rr->ttl == 0)) - return -EINVAL; - } - } - - return dns_answer_add_raw(a, rr, ifindex, flags); -} - -static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) { - DnsResourceRecord *rr; - DnsAnswerFlags flags; - int ifindex, r; - - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, b) { - r = dns_answer_add(a, rr, ifindex, flags); - if (r < 0) - return r; - } - - return 0; -} - -int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { - int r; - - assert(a); - assert(rr); - - r = dns_answer_reserve_or_clone(a, 1); - if (r < 0) - return r; - - return dns_answer_add(*a, rr, ifindex, flags); -} - -int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl, int ifindex) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *soa = NULL; - - soa = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_SOA, name); - if (!soa) - return -ENOMEM; - - soa->ttl = ttl; - - soa->soa.mname = strdup(name); - if (!soa->soa.mname) - return -ENOMEM; - - soa->soa.rname = strappend("root.", name); - if (!soa->soa.rname) - return -ENOMEM; - - soa->soa.serial = 1; - soa->soa.refresh = 1; - soa->soa.retry = 1; - soa->soa.expire = 1; - soa->soa.minimum = ttl; - - return dns_answer_add(a, soa, ifindex, DNS_ANSWER_AUTHENTICATED); -} - -int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags) { - DnsAnswerFlags flags = 0, i_flags; - DnsResourceRecord *i; - bool found = false; - int r; - - assert(key); - - DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) { - r = dns_resource_key_match_rr(key, i, NULL); - if (r < 0) - return r; - if (r == 0) - continue; - - if (!ret_flags) - return 1; - - if (found) - flags &= i_flags; - else { - flags = i_flags; - found = true; - } - } - - if (ret_flags) - *ret_flags = flags; - - return found; -} - -int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr, DnsAnswerFlags *ret_flags) { - DnsAnswerFlags flags = 0, i_flags; - DnsResourceRecord *i; - bool found = false; - int r; - - assert(rr); - - DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) { - r = dns_resource_record_equal(i, rr); - if (r < 0) - return r; - if (r == 0) - continue; - - if (!ret_flags) - return 1; - - if (found) - flags &= i_flags; - else { - flags = i_flags; - found = true; - } - } - - if (ret_flags) - *ret_flags = flags; - - return found; -} - -int dns_answer_contains_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags) { - DnsAnswerFlags flags = 0, i_flags; - DnsResourceRecord *i; - bool found = false; - int r; - - assert(key); - - DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) { - r = dns_resource_key_equal(i->key, key); - if (r < 0) - return r; - if (r == 0) - continue; - - if (!ret_flags) - return true; - - if (found) - flags &= i_flags; - else { - flags = i_flags; - found = true; - } - } - - if (ret_flags) - *ret_flags = flags; - - return found; -} - -int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a) { - DnsResourceRecord *i; - - DNS_ANSWER_FOREACH(i, a) { - if (IN_SET(i->key->type, DNS_TYPE_NSEC, DNS_TYPE_NSEC3)) - return true; - } - - return false; -} - -int dns_answer_contains_zone_nsec3(DnsAnswer *answer, const char *zone) { - DnsResourceRecord *rr; - int r; - - /* Checks whether the specified answer contains at least one NSEC3 RR in the specified zone */ - - DNS_ANSWER_FOREACH(rr, answer) { - const char *p; - - if (rr->key->type != DNS_TYPE_NSEC3) - continue; - - p = dns_resource_key_name(rr->key); - r = dns_name_parent(&p); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dns_name_equal(p, zone); - if (r != 0) - return r; - } - - return false; -} - -int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) { - DnsResourceRecord *rr, *soa = NULL; - DnsAnswerFlags rr_flags, soa_flags = 0; - int r; - - assert(key); - - /* For a SOA record we can never find a matching SOA record */ - if (key->type == DNS_TYPE_SOA) - return 0; - - DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) { - r = dns_resource_key_match_soa(key, rr->key); - if (r < 0) - return r; - if (r > 0) { - - if (soa) { - r = dns_name_endswith(dns_resource_key_name(rr->key), dns_resource_key_name(soa->key)); - if (r < 0) - return r; - if (r > 0) - continue; - } - - soa = rr; - soa_flags = rr_flags; - } - } - - if (!soa) - return 0; - - if (ret) - *ret = soa; - if (flags) - *flags = soa_flags; - - return 1; -} - -int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) { - DnsResourceRecord *rr; - DnsAnswerFlags rr_flags; - int r; - - assert(key); - - /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */ - if (!dns_type_may_redirect(key->type)) - return 0; - - DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) { - r = dns_resource_key_match_cname_or_dname(key, rr->key, NULL); - if (r < 0) - return r; - if (r > 0) { - if (ret) - *ret = rr; - if (flags) - *flags = rr_flags; - return 1; - } - } - - return 0; -} - -int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret) { - _cleanup_(dns_answer_unrefp) DnsAnswer *k = NULL; - int r; - - assert(ret); - - if (dns_answer_size(a) <= 0) { - *ret = dns_answer_ref(b); - return 0; - } - - if (dns_answer_size(b) <= 0) { - *ret = dns_answer_ref(a); - return 0; - } - - k = dns_answer_new(a->n_rrs + b->n_rrs); - if (!k) - return -ENOMEM; - - r = dns_answer_add_raw_all(k, a); - if (r < 0) - return r; - - r = dns_answer_add_all(k, b); - if (r < 0) - return r; - - *ret = k; - k = NULL; - - return 0; -} - -int dns_answer_extend(DnsAnswer **a, DnsAnswer *b) { - DnsAnswer *merged; - int r; - - assert(a); - - r = dns_answer_merge(*a, b, &merged); - if (r < 0) - return r; - - dns_answer_unref(*a); - *a = merged; - - return 0; -} - -int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key) { - bool found = false, other = false; - DnsResourceRecord *rr; - unsigned i; - int r; - - assert(a); - assert(key); - - /* Remove all entries matching the specified key from *a */ - - DNS_ANSWER_FOREACH(rr, *a) { - r = dns_resource_key_equal(rr->key, key); - if (r < 0) - return r; - if (r > 0) - found = true; - else - other = true; - - if (found && other) - break; - } - - if (!found) - return 0; - - if (!other) { - *a = dns_answer_unref(*a); /* Return NULL for the empty answer */ - return 1; - } - - if ((*a)->n_ref > 1) { - _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL; - DnsAnswerFlags flags; - int ifindex; - - copy = dns_answer_new((*a)->n_rrs); - if (!copy) - return -ENOMEM; - - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) { - r = dns_resource_key_equal(rr->key, key); - if (r < 0) - return r; - if (r > 0) - continue; - - r = dns_answer_add_raw(copy, rr, ifindex, flags); - if (r < 0) - return r; - } - - dns_answer_unref(*a); - *a = copy; - copy = NULL; - - return 1; - } - - /* Only a single reference, edit in-place */ - - i = 0; - for (;;) { - if (i >= (*a)->n_rrs) - break; - - r = dns_resource_key_equal((*a)->items[i].rr->key, key); - if (r < 0) - return r; - if (r > 0) { - /* Kill this entry */ - - dns_resource_record_unref((*a)->items[i].rr); - memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1)); - (*a)->n_rrs--; - continue; - - } else - /* Keep this entry */ - i++; - } - - return 1; -} - -int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rm) { - bool found = false, other = false; - DnsResourceRecord *rr; - unsigned i; - int r; - - assert(a); - assert(rm); - - /* Remove all entries matching the specified RR from *a */ - - DNS_ANSWER_FOREACH(rr, *a) { - r = dns_resource_record_equal(rr, rm); - if (r < 0) - return r; - if (r > 0) - found = true; - else - other = true; - - if (found && other) - break; - } - - if (!found) - return 0; - - if (!other) { - *a = dns_answer_unref(*a); /* Return NULL for the empty answer */ - return 1; - } - - if ((*a)->n_ref > 1) { - _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL; - DnsAnswerFlags flags; - int ifindex; - - copy = dns_answer_new((*a)->n_rrs); - if (!copy) - return -ENOMEM; - - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) { - r = dns_resource_record_equal(rr, rm); - if (r < 0) - return r; - if (r > 0) - continue; - - r = dns_answer_add_raw(copy, rr, ifindex, flags); - if (r < 0) - return r; - } - - dns_answer_unref(*a); - *a = copy; - copy = NULL; - - return 1; - } - - /* Only a single reference, edit in-place */ - - i = 0; - for (;;) { - if (i >= (*a)->n_rrs) - break; - - r = dns_resource_record_equal((*a)->items[i].rr, rm); - if (r < 0) - return r; - if (r > 0) { - /* Kill this entry */ - - dns_resource_record_unref((*a)->items[i].rr); - memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1)); - (*a)->n_rrs--; - continue; - - } else - /* Keep this entry */ - i++; - } - - return 1; -} - -int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags) { - DnsResourceRecord *rr_source; - int ifindex_source, r; - DnsAnswerFlags flags_source; - - assert(a); - assert(key); - - /* Copy all RRs matching the specified key from source into *a */ - - DNS_ANSWER_FOREACH_FULL(rr_source, ifindex_source, flags_source, source) { - - r = dns_resource_key_equal(rr_source->key, key); - if (r < 0) - return r; - if (r == 0) - continue; - - /* Make space for at least one entry */ - r = dns_answer_reserve_or_clone(a, 1); - if (r < 0) - return r; - - r = dns_answer_add(*a, rr_source, ifindex_source, flags_source|or_flags); - if (r < 0) - return r; - } - - return 0; -} - -int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags) { - int r; - - assert(to); - assert(from); - assert(key); - - r = dns_answer_copy_by_key(to, *from, key, or_flags); - if (r < 0) - return r; - - return dns_answer_remove_by_key(from, key); -} - -void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local) { - DnsAnswerItem *items; - unsigned i, start, end; - - if (!a) - return; - - if (a->n_rrs <= 1) - return; - - start = 0; - end = a->n_rrs-1; - - /* RFC 4795, Section 2.6 suggests we should order entries - * depending on whether the sender is a link-local address. */ - - items = newa(DnsAnswerItem, a->n_rrs); - for (i = 0; i < a->n_rrs; i++) { - - if (a->items[i].rr->key->class == DNS_CLASS_IN && - ((a->items[i].rr->key->type == DNS_TYPE_A && in_addr_is_link_local(AF_INET, (union in_addr_union*) &a->items[i].rr->a.in_addr) != prefer_link_local) || - (a->items[i].rr->key->type == DNS_TYPE_AAAA && in_addr_is_link_local(AF_INET6, (union in_addr_union*) &a->items[i].rr->aaaa.in6_addr) != prefer_link_local))) - /* Order address records that are not preferred to the end of the array */ - items[end--] = a->items[i]; - else - /* Order all other records to the beginning of the array */ - items[start++] = a->items[i]; - } - - assert(start == end+1); - memcpy(a->items, items, sizeof(DnsAnswerItem) * a->n_rrs); -} - -int dns_answer_reserve(DnsAnswer **a, unsigned n_free) { - DnsAnswer *n; - - assert(a); - - if (n_free <= 0) - return 0; - - if (*a) { - unsigned ns; - - if ((*a)->n_ref > 1) - return -EBUSY; - - ns = (*a)->n_rrs + n_free; - - if ((*a)->n_allocated >= ns) - return 0; - - /* Allocate more than we need */ - ns *= 2; - - n = realloc(*a, offsetof(DnsAnswer, items) + sizeof(DnsAnswerItem) * ns); - if (!n) - return -ENOMEM; - - n->n_allocated = ns; - } else { - n = dns_answer_new(n_free); - if (!n) - return -ENOMEM; - } - - *a = n; - return 0; -} - -int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free) { - _cleanup_(dns_answer_unrefp) DnsAnswer *n = NULL; - int r; - - assert(a); - - /* Tries to extend the DnsAnswer object. And if that's not - * possible, since we are not the sole owner, then allocate a - * new, appropriately sized one. Either way, after this call - * the object will only have a single reference, and has room - * for at least the specified number of RRs. */ - - r = dns_answer_reserve(a, n_free); - if (r != -EBUSY) - return r; - - assert(*a); - - n = dns_answer_new(((*a)->n_rrs + n_free) * 2); - if (!n) - return -ENOMEM; - - r = dns_answer_add_raw_all(n, *a); - if (r < 0) - return r; - - dns_answer_unref(*a); - *a = n; - n = NULL; - - return 0; -} - -void dns_answer_dump(DnsAnswer *answer, FILE *f) { - DnsResourceRecord *rr; - DnsAnswerFlags flags; - int ifindex; - - if (!f) - f = stdout; - - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) { - const char *t; - - fputc('\t', f); - - t = dns_resource_record_to_string(rr); - if (!t) { - log_oom(); - continue; - } - - fputs(t, f); - - if (ifindex != 0 || flags & (DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE|DNS_ANSWER_SHARED_OWNER)) - fputs("\t;", f); - - if (ifindex != 0) - printf(" ifindex=%i", ifindex); - if (flags & DNS_ANSWER_AUTHENTICATED) - fputs(" authenticated", f); - if (flags & DNS_ANSWER_CACHEABLE) - fputs(" cachable", f); - if (flags & DNS_ANSWER_SHARED_OWNER) - fputs(" shared-owner", f); - - fputc('\n', f); - } -} - -bool dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname) { - DnsResourceRecord *rr; - int r; - - assert(cname); - - /* Checks whether the answer contains a DNAME record that indicates that the specified CNAME record is - * synthesized from it */ - - if (cname->key->type != DNS_TYPE_CNAME) - return 0; - - DNS_ANSWER_FOREACH(rr, a) { - _cleanup_free_ char *n = NULL; - - if (rr->key->type != DNS_TYPE_DNAME) - continue; - if (rr->key->class != cname->key->class) - continue; - - r = dns_name_change_suffix(cname->cname.name, rr->dname.name, dns_resource_key_name(rr->key), &n); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dns_name_equal(n, dns_resource_key_name(cname->key)); - if (r < 0) - return r; - if (r > 0) - return 1; - - } - - return 0; -} diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h deleted file mode 100644 index 4a92bd1150..0000000000 --- a/src/resolve/resolved-dns-answer.h +++ /dev/null @@ -1,147 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -typedef struct DnsAnswer DnsAnswer; -typedef struct DnsAnswerItem DnsAnswerItem; - -#include "macro.h" -#include "resolved-dns-rr.h" - -/* A simple array of resource records. We keep track of the - * originating ifindex for each RR where that makes sense, so that we - * can qualify A and AAAA RRs referring to a local link with the - * right ifindex. - * - * Note that we usually encode the empty DnsAnswer object as a simple NULL. */ - -typedef enum DnsAnswerFlags { - DNS_ANSWER_AUTHENTICATED = 1, /* Item has been authenticated */ - DNS_ANSWER_CACHEABLE = 2, /* Item is subject to caching */ - DNS_ANSWER_SHARED_OWNER = 4, /* For mDNS: RRset may be owner by multiple peers */ -} DnsAnswerFlags; - -struct DnsAnswerItem { - DnsResourceRecord *rr; - int ifindex; - DnsAnswerFlags flags; -}; - -struct DnsAnswer { - unsigned n_ref; - unsigned n_rrs, n_allocated; - DnsAnswerItem items[0]; -}; - -DnsAnswer *dns_answer_new(unsigned n); -DnsAnswer *dns_answer_ref(DnsAnswer *a); -DnsAnswer *dns_answer_unref(DnsAnswer *a); - -int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags); -int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags); -int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl, int ifindex); - -int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *combined_flags); -int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr, DnsAnswerFlags *combined_flags); -int dns_answer_contains_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *combined_flags); -int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a); -int dns_answer_contains_zone_nsec3(DnsAnswer *answer, const char *zone); - -int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags); -int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags); - -int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret); -int dns_answer_extend(DnsAnswer **a, DnsAnswer *b); - -void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local); - -int dns_answer_reserve(DnsAnswer **a, unsigned n_free); -int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free); - -int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key); -int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rr); - -int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags); -int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags); - -bool dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname); - -static inline unsigned dns_answer_size(DnsAnswer *a) { - return a ? a->n_rrs : 0; -} - -static inline bool dns_answer_isempty(DnsAnswer *a) { - return dns_answer_size(a) <= 0; -} - -void dns_answer_dump(DnsAnswer *answer, FILE *f); - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref); - -#define _DNS_ANSWER_FOREACH(q, kk, a) \ - for (unsigned UNIQ_T(i, q) = ({ \ - (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ - 0; \ - }); \ - (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ - UNIQ_T(i, q)++, (kk) = (UNIQ_T(i, q) < (a)->n_rrs ? (a)->items[UNIQ_T(i, q)].rr : NULL)) - -#define DNS_ANSWER_FOREACH(kk, a) _DNS_ANSWER_FOREACH(UNIQ, kk, a) - -#define _DNS_ANSWER_FOREACH_IFINDEX(q, kk, ifi, a) \ - for (unsigned UNIQ_T(i, q) = ({ \ - (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ - (ifi) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \ - 0; \ - }); \ - (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ - UNIQ_T(i, q)++, \ - (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ - (ifi) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0)) - -#define DNS_ANSWER_FOREACH_IFINDEX(kk, ifindex, a) _DNS_ANSWER_FOREACH_IFINDEX(UNIQ, kk, ifindex, a) - -#define _DNS_ANSWER_FOREACH_FLAGS(q, kk, fl, a) \ - for (unsigned UNIQ_T(i, q) = ({ \ - (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ - (fl) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].flags : 0; \ - 0; \ - }); \ - (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ - UNIQ_T(i, q)++, \ - (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ - (fl) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].flags : 0)) - -#define DNS_ANSWER_FOREACH_FLAGS(kk, flags, a) _DNS_ANSWER_FOREACH_FLAGS(UNIQ, kk, flags, a) - -#define _DNS_ANSWER_FOREACH_FULL(q, kk, ifi, fl, a) \ - for (unsigned UNIQ_T(i, q) = ({ \ - (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ - (ifi) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \ - (fl) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].flags : 0; \ - 0; \ - }); \ - (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ - UNIQ_T(i, q)++, \ - (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ - (ifi) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0), \ - (fl) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].flags : 0)) - -#define DNS_ANSWER_FOREACH_FULL(kk, ifindex, flags, a) _DNS_ANSWER_FOREACH_FULL(UNIQ, kk, ifindex, flags, a) diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c deleted file mode 100644 index 9233fb0ac1..0000000000 --- a/src/resolve/resolved-dns-cache.c +++ /dev/null @@ -1,1064 +0,0 @@ -/*** - 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 <net/if.h> - -#include "af-list.h" -#include "alloc-util.h" -#include "dns-domain.h" -#include "resolved-dns-answer.h" -#include "resolved-dns-cache.h" -#include "resolved-dns-packet.h" -#include "string-util.h" - -/* Never cache more than 4K entries. RFC 1536, Section 5 suggests to - * leave DNS caches unbounded, but that's crazy. */ -#define CACHE_MAX 4096 - -/* We never keep any item longer than 2h in our cache */ -#define CACHE_TTL_MAX_USEC (2 * USEC_PER_HOUR) - -typedef enum DnsCacheItemType DnsCacheItemType; -typedef struct DnsCacheItem DnsCacheItem; - -enum DnsCacheItemType { - DNS_CACHE_POSITIVE, - DNS_CACHE_NODATA, - DNS_CACHE_NXDOMAIN, -}; - -struct DnsCacheItem { - DnsCacheItemType type; - DnsResourceKey *key; - DnsResourceRecord *rr; - - usec_t until; - bool authenticated:1; - bool shared_owner:1; - - int ifindex; - int owner_family; - union in_addr_union owner_address; - - unsigned prioq_idx; - LIST_FIELDS(DnsCacheItem, by_key); -}; - -static void dns_cache_item_free(DnsCacheItem *i) { - if (!i) - return; - - dns_resource_record_unref(i->rr); - dns_resource_key_unref(i->key); - free(i); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem*, dns_cache_item_free); - -static void dns_cache_item_unlink_and_free(DnsCache *c, DnsCacheItem *i) { - DnsCacheItem *first; - - assert(c); - - if (!i) - return; - - first = hashmap_get(c->by_key, i->key); - LIST_REMOVE(by_key, first, i); - - if (first) - assert_se(hashmap_replace(c->by_key, first->key, first) >= 0); - else - hashmap_remove(c->by_key, i->key); - - prioq_remove(c->by_expiry, i, &i->prioq_idx); - - dns_cache_item_free(i); -} - -static bool dns_cache_remove_by_rr(DnsCache *c, DnsResourceRecord *rr) { - DnsCacheItem *first, *i; - int r; - - first = hashmap_get(c->by_key, rr->key); - LIST_FOREACH(by_key, i, first) { - r = dns_resource_record_equal(i->rr, rr); - if (r < 0) - return r; - if (r > 0) { - dns_cache_item_unlink_and_free(c, i); - return true; - } - } - - return false; -} - -static bool dns_cache_remove_by_key(DnsCache *c, DnsResourceKey *key) { - DnsCacheItem *first, *i, *n; - - assert(c); - assert(key); - - first = hashmap_remove(c->by_key, key); - if (!first) - return false; - - LIST_FOREACH_SAFE(by_key, i, n, first) { - prioq_remove(c->by_expiry, i, &i->prioq_idx); - dns_cache_item_free(i); - } - - return true; -} - -void dns_cache_flush(DnsCache *c) { - DnsResourceKey *key; - - assert(c); - - while ((key = hashmap_first_key(c->by_key))) - dns_cache_remove_by_key(c, key); - - assert(hashmap_size(c->by_key) == 0); - assert(prioq_size(c->by_expiry) == 0); - - c->by_key = hashmap_free(c->by_key); - c->by_expiry = prioq_free(c->by_expiry); -} - -static void dns_cache_make_space(DnsCache *c, unsigned add) { - assert(c); - - if (add <= 0) - return; - - /* Makes space for n new entries. Note that we actually allow - * the cache to grow beyond CACHE_MAX, but only when we shall - * add more RRs to the cache than CACHE_MAX at once. In that - * case the cache will be emptied completely otherwise. */ - - for (;;) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - DnsCacheItem *i; - - if (prioq_size(c->by_expiry) <= 0) - break; - - if (prioq_size(c->by_expiry) + add < CACHE_MAX) - break; - - i = prioq_peek(c->by_expiry); - assert(i); - - /* Take an extra reference to the key so that it - * doesn't go away in the middle of the remove call */ - key = dns_resource_key_ref(i->key); - dns_cache_remove_by_key(c, key); - } -} - -void dns_cache_prune(DnsCache *c) { - usec_t t = 0; - - assert(c); - - /* Remove all entries that are past their TTL */ - - for (;;) { - DnsCacheItem *i; - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - - i = prioq_peek(c->by_expiry); - if (!i) - break; - - if (t <= 0) - t = now(clock_boottime_or_monotonic()); - - if (i->until > t) - break; - - /* Depending whether this is an mDNS shared entry - * either remove only this one RR or the whole RRset */ - log_debug("Removing %scache entry for %s (expired "USEC_FMT"s ago)", - i->shared_owner ? "shared " : "", - dns_resource_key_to_string(i->key, key_str, sizeof key_str), - (t - i->until) / USEC_PER_SEC); - - if (i->shared_owner) - dns_cache_item_unlink_and_free(c, i); - else { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - - /* Take an extra reference to the key so that it - * doesn't go away in the middle of the remove call */ - key = dns_resource_key_ref(i->key); - dns_cache_remove_by_key(c, key); - } - } -} - -static int dns_cache_item_prioq_compare_func(const void *a, const void *b) { - const DnsCacheItem *x = a, *y = b; - - if (x->until < y->until) - return -1; - if (x->until > y->until) - return 1; - return 0; -} - -static int dns_cache_init(DnsCache *c) { - int r; - - assert(c); - - r = prioq_ensure_allocated(&c->by_expiry, dns_cache_item_prioq_compare_func); - if (r < 0) - return r; - - r = hashmap_ensure_allocated(&c->by_key, &dns_resource_key_hash_ops); - if (r < 0) - return r; - - return r; -} - -static int dns_cache_link_item(DnsCache *c, DnsCacheItem *i) { - DnsCacheItem *first; - int r; - - assert(c); - assert(i); - - r = prioq_put(c->by_expiry, i, &i->prioq_idx); - if (r < 0) - return r; - - first = hashmap_get(c->by_key, i->key); - if (first) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; - - /* Keep a reference to the original key, while we manipulate the list. */ - k = dns_resource_key_ref(first->key); - - /* Now, try to reduce the number of keys we keep */ - dns_resource_key_reduce(&first->key, &i->key); - - if (first->rr) - dns_resource_key_reduce(&first->rr->key, &i->key); - if (i->rr) - dns_resource_key_reduce(&i->rr->key, &i->key); - - LIST_PREPEND(by_key, first, i); - assert_se(hashmap_replace(c->by_key, first->key, first) >= 0); - } else { - r = hashmap_put(c->by_key, i->key, i); - if (r < 0) { - prioq_remove(c->by_expiry, i, &i->prioq_idx); - return r; - } - } - - return 0; -} - -static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) { - DnsCacheItem *i; - - assert(c); - assert(rr); - - LIST_FOREACH(by_key, i, hashmap_get(c->by_key, rr->key)) - if (i->rr && dns_resource_record_equal(i->rr, rr) > 0) - return i; - - return NULL; -} - -static usec_t calculate_until(DnsResourceRecord *rr, uint32_t nsec_ttl, usec_t timestamp, bool use_soa_minimum) { - uint32_t ttl; - usec_t u; - - assert(rr); - - ttl = MIN(rr->ttl, nsec_ttl); - if (rr->key->type == DNS_TYPE_SOA && use_soa_minimum) { - /* If this is a SOA RR, and it is requested, clamp to - * the SOA's minimum field. This is used when we do - * negative caching, to determine the TTL for the - * negative caching entry. See RFC 2308, Section - * 5. */ - - if (ttl > rr->soa.minimum) - ttl = rr->soa.minimum; - } - - u = ttl * USEC_PER_SEC; - if (u > CACHE_TTL_MAX_USEC) - u = CACHE_TTL_MAX_USEC; - - if (rr->expiry != USEC_INFINITY) { - usec_t left; - - /* Make use of the DNSSEC RRSIG expiry info, if we - * have it */ - - left = LESS_BY(rr->expiry, now(CLOCK_REALTIME)); - if (u > left) - u = left; - } - - return timestamp + u; -} - -static void dns_cache_item_update_positive( - DnsCache *c, - DnsCacheItem *i, - DnsResourceRecord *rr, - bool authenticated, - bool shared_owner, - usec_t timestamp, - int ifindex, - int owner_family, - const union in_addr_union *owner_address) { - - assert(c); - assert(i); - assert(rr); - assert(owner_address); - - i->type = DNS_CACHE_POSITIVE; - - if (!i->by_key_prev) - /* We are the first item in the list, we need to - * update the key used in the hashmap */ - - assert_se(hashmap_replace(c->by_key, rr->key, i) >= 0); - - dns_resource_record_ref(rr); - dns_resource_record_unref(i->rr); - i->rr = rr; - - dns_resource_key_unref(i->key); - i->key = dns_resource_key_ref(rr->key); - - i->until = calculate_until(rr, (uint32_t) -1, timestamp, false); - i->authenticated = authenticated; - i->shared_owner = shared_owner; - - i->ifindex = ifindex; - - i->owner_family = owner_family; - i->owner_address = *owner_address; - - prioq_reshuffle(c->by_expiry, i, &i->prioq_idx); -} - -static int dns_cache_put_positive( - DnsCache *c, - DnsResourceRecord *rr, - bool authenticated, - bool shared_owner, - usec_t timestamp, - int ifindex, - int owner_family, - const union in_addr_union *owner_address) { - - _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL; - DnsCacheItem *existing; - char key_str[DNS_RESOURCE_KEY_STRING_MAX], ifname[IF_NAMESIZE]; - int r, k; - - assert(c); - assert(rr); - assert(owner_address); - - /* Never cache pseudo RRs */ - if (dns_class_is_pseudo(rr->key->class)) - return 0; - if (dns_type_is_pseudo(rr->key->type)) - return 0; - - /* New TTL is 0? Delete this specific entry... */ - if (rr->ttl <= 0) { - k = dns_cache_remove_by_rr(c, rr); - log_debug("%s: %s", - k > 0 ? "Removed zero TTL entry from cache" : "Not caching zero TTL cache entry", - dns_resource_key_to_string(rr->key, key_str, sizeof key_str)); - return 0; - } - - /* Entry exists already? Update TTL, timestamp and owner*/ - existing = dns_cache_get(c, rr); - if (existing) { - dns_cache_item_update_positive( - c, - existing, - rr, - authenticated, - shared_owner, - timestamp, - ifindex, - owner_family, - owner_address); - return 0; - } - - /* Otherwise, add the new RR */ - r = dns_cache_init(c); - if (r < 0) - return r; - - dns_cache_make_space(c, 1); - - i = new0(DnsCacheItem, 1); - if (!i) - return -ENOMEM; - - i->type = DNS_CACHE_POSITIVE; - i->key = dns_resource_key_ref(rr->key); - i->rr = dns_resource_record_ref(rr); - i->until = calculate_until(rr, (uint32_t) -1, timestamp, false); - i->authenticated = authenticated; - i->shared_owner = shared_owner; - i->ifindex = ifindex; - i->owner_family = owner_family; - i->owner_address = *owner_address; - i->prioq_idx = PRIOQ_IDX_NULL; - - r = dns_cache_link_item(c, i); - if (r < 0) - return r; - - if (log_get_max_level() >= LOG_DEBUG) { - _cleanup_free_ char *t = NULL; - - (void) in_addr_to_string(i->owner_family, &i->owner_address, &t); - - log_debug("Added positive %s%s cache entry for %s "USEC_FMT"s on %s/%s/%s", - i->authenticated ? "authenticated" : "unauthenticated", - i->shared_owner ? " shared" : "", - dns_resource_key_to_string(i->key, key_str, sizeof key_str), - (i->until - timestamp) / USEC_PER_SEC, - i->ifindex == 0 ? "*" : strna(if_indextoname(i->ifindex, ifname)), - af_to_name_short(i->owner_family), - strna(t)); - } - - i = NULL; - return 0; -} - -static int dns_cache_put_negative( - DnsCache *c, - DnsResourceKey *key, - int rcode, - bool authenticated, - uint32_t nsec_ttl, - usec_t timestamp, - DnsResourceRecord *soa, - int owner_family, - const union in_addr_union *owner_address) { - - _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL; - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - int r; - - assert(c); - assert(key); - assert(soa); - assert(owner_address); - - /* Never cache pseudo RR keys. DNS_TYPE_ANY is particularly - * important to filter out as we use this as a pseudo-type for - * NXDOMAIN entries */ - if (dns_class_is_pseudo(key->class)) - return 0; - if (dns_type_is_pseudo(key->type)) - return 0; - - if (nsec_ttl <= 0 || soa->soa.minimum <= 0 || soa->ttl <= 0) { - log_debug("Not caching negative entry with zero SOA/NSEC/NSEC3 TTL: %s", - dns_resource_key_to_string(key, key_str, sizeof key_str)); - return 0; - } - - if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) - return 0; - - r = dns_cache_init(c); - if (r < 0) - return r; - - dns_cache_make_space(c, 1); - - i = new0(DnsCacheItem, 1); - if (!i) - return -ENOMEM; - - i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN; - i->until = calculate_until(soa, nsec_ttl, timestamp, true); - i->authenticated = authenticated; - i->owner_family = owner_family; - i->owner_address = *owner_address; - i->prioq_idx = PRIOQ_IDX_NULL; - - if (i->type == DNS_CACHE_NXDOMAIN) { - /* NXDOMAIN entries should apply equally to all types, so we use ANY as - * a pseudo type for this purpose here. */ - i->key = dns_resource_key_new(key->class, DNS_TYPE_ANY, dns_resource_key_name(key)); - if (!i->key) - return -ENOMEM; - - /* Make sure to remove any previous entry for this - * specific ANY key. (For non-ANY keys the cache data - * is already cleared by the caller.) Note that we - * don't bother removing positive or NODATA cache - * items in this case, because it would either be slow - * or require explicit indexing by name */ - dns_cache_remove_by_key(c, key); - } else - i->key = dns_resource_key_ref(key); - - r = dns_cache_link_item(c, i); - if (r < 0) - return r; - - log_debug("Added %s cache entry for %s "USEC_FMT"s", - i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", - dns_resource_key_to_string(i->key, key_str, sizeof key_str), - (i->until - timestamp) / USEC_PER_SEC); - - i = NULL; - return 0; -} - -static void dns_cache_remove_previous( - DnsCache *c, - DnsResourceKey *key, - DnsAnswer *answer) { - - DnsResourceRecord *rr; - DnsAnswerFlags flags; - - assert(c); - - /* First, if we were passed a key (i.e. on LLMNR/DNS, but - * not on mDNS), delete all matching old RRs, so that we only - * keep complete by_key in place. */ - if (key) - dns_cache_remove_by_key(c, key); - - /* Second, flush all entries matching the answer, unless this - * is an RR that is explicitly marked to be "shared" between - * peers (i.e. mDNS RRs without the flush-cache bit set). */ - DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { - if ((flags & DNS_ANSWER_CACHEABLE) == 0) - continue; - - if (flags & DNS_ANSWER_SHARED_OWNER) - continue; - - dns_cache_remove_by_key(c, rr->key); - } -} - -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, - int rcode, - DnsAnswer *answer, - bool authenticated, - uint32_t nsec_ttl, - usec_t timestamp, - int owner_family, - const union in_addr_union *owner_address) { - - DnsResourceRecord *soa = NULL, *rr; - DnsAnswerFlags flags; - unsigned cache_keys; - int r, ifindex; - - assert(c); - assert(owner_address); - - dns_cache_remove_previous(c, key, answer); - - /* We only care for positive replies and NXDOMAINs, on all - * other replies we will simply flush the respective entries, - * and that's it */ - if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) - return 0; - - if (dns_answer_size(answer) <= 0) { - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - - log_debug("Not caching negative entry without a SOA record: %s", - dns_resource_key_to_string(key, key_str, sizeof key_str)); - return 0; - } - - cache_keys = dns_answer_size(answer); - if (key) - cache_keys++; - - /* Make some space for our new entries */ - dns_cache_make_space(c, cache_keys); - - if (timestamp <= 0) - timestamp = now(clock_boottime_or_monotonic()); - - /* Second, add in positive entries for all contained RRs */ - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) { - 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, - flags & DNS_ANSWER_AUTHENTICATED, - flags & DNS_ANSWER_SHARED_OWNER, - timestamp, - ifindex, - owner_family, owner_address); - if (r < 0) - goto fail; - } - - if (!key) /* mDNS doesn't know negative caching, really */ - return 0; - - /* Third, add in negative entries if the key has no RR */ - r = dns_answer_match_key(answer, key, NULL); - if (r < 0) - goto fail; - if (r > 0) - return 0; - - /* But not if it has a matching CNAME/DNAME (the negative - * caching will be done on the canonical name, not on the - * alias) */ - r = dns_answer_find_cname_or_dname(answer, key, NULL, NULL); - if (r < 0) - goto fail; - if (r > 0) - return 0; - - /* See https://tools.ietf.org/html/rfc2308, which say that a - * matching SOA record in the packet is used to enable - * negative caching. */ - r = dns_answer_find_soa(answer, key, &soa, &flags); - if (r < 0) - goto fail; - if (r == 0) - return 0; - - /* Refuse using the SOA data if it is unsigned, but the key is - * signed */ - if (authenticated && (flags & DNS_ANSWER_AUTHENTICATED) == 0) - return 0; - - r = dns_cache_put_negative( - c, - key, - rcode, - authenticated, - nsec_ttl, - timestamp, - soa, - owner_family, owner_address); - if (r < 0) - goto fail; - - return 0; - -fail: - /* Adding all RRs failed. Let's clean up what we already - * added, just in case */ - - if (key) - dns_cache_remove_by_key(c, key); - - DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { - if ((flags & DNS_ANSWER_CACHEABLE) == 0) - continue; - - dns_cache_remove_by_key(c, rr->key); - } - - return r; -} - -static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, DnsResourceKey *k) { - DnsCacheItem *i; - const char *n; - int r; - - assert(c); - assert(k); - - /* If we hit some OOM error, or suchlike, we don't care too - * much, after all this is just a cache */ - - i = hashmap_get(c->by_key, k); - if (i) - return i; - - n = dns_resource_key_name(k); - - /* Check if we have an NXDOMAIN cache item for the name, notice that we use - * the pseudo-type ANY for NXDOMAIN cache items. */ - i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_ANY, n)); - if (i && i->type == DNS_CACHE_NXDOMAIN) - return i; - - if (dns_type_may_redirect(k->type)) { - /* Check if we have a CNAME record instead */ - i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_CNAME, n)); - if (i) - return i; - - /* OK, let's look for cached DNAME records. */ - for (;;) { - if (isempty(n)) - return NULL; - - i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_DNAME, n)); - if (i) - return i; - - /* Jump one label ahead */ - r = dns_name_parent(&n); - if (r <= 0) - return NULL; - } - } - - if (k->type != DNS_TYPE_NSEC) { - /* Check if we have an NSEC record instead for the name. */ - i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_NSEC, n)); - if (i) - return i; - } - - return NULL; -} - -int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcode, DnsAnswer **ret, bool *authenticated) { - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - unsigned n = 0; - int r; - bool nxdomain = false; - DnsCacheItem *j, *first, *nsec = NULL; - bool have_authenticated = false, have_non_authenticated = false; - usec_t current; - - assert(c); - assert(key); - assert(rcode); - assert(ret); - assert(authenticated); - - if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) { - /* If we have ANY lookups we don't use the cache, so - * that the caller refreshes via the network. */ - - log_debug("Ignoring cache for ANY lookup: %s", - dns_resource_key_to_string(key, key_str, sizeof key_str)); - - c->n_miss++; - - *ret = NULL; - *rcode = DNS_RCODE_SUCCESS; - return 0; - } - - first = dns_cache_get_by_key_follow_cname_dname_nsec(c, key); - if (!first) { - /* If one question cannot be answered we need to refresh */ - - log_debug("Cache miss for %s", - dns_resource_key_to_string(key, key_str, sizeof key_str)); - - c->n_miss++; - - *ret = NULL; - *rcode = DNS_RCODE_SUCCESS; - return 0; - } - - LIST_FOREACH(by_key, j, first) { - if (j->rr) { - if (j->rr->key->type == DNS_TYPE_NSEC) - nsec = j; - - n++; - } else if (j->type == DNS_CACHE_NXDOMAIN) - nxdomain = true; - - if (j->authenticated) - have_authenticated = true; - else - have_non_authenticated = true; - } - - 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. */ - - log_debug("NSEC NODATA cache hit for %s", - dns_resource_key_to_string(key, key_str, sizeof key_str)); - - /* We only found an NSEC record that matches our name. - * If it says the type doesn't exist report - * NODATA. Otherwise report a cache miss. */ - - *ret = NULL; - *rcode = DNS_RCODE_SUCCESS; - *authenticated = nsec->authenticated; - - if (!bitmap_isset(nsec->rr->nsec.types, key->type) && - !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_CNAME) && - !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_DNAME)) { - c->n_hit++; - return 1; - } - - c->n_miss++; - return 0; - } - - log_debug("%s cache hit for %s", - n > 0 ? "Positive" : - nxdomain ? "NXDOMAIN" : "NODATA", - dns_resource_key_to_string(key, key_str, sizeof key_str)); - - if (n <= 0) { - c->n_hit++; - - *ret = NULL; - *rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS; - *authenticated = have_authenticated && !have_non_authenticated; - return 1; - } - - answer = dns_answer_new(n); - if (!answer) - return -ENOMEM; - - if (clamp_ttl) - current = now(clock_boottime_or_monotonic()); - - LIST_FOREACH(by_key, j, first) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - if (!j->rr) - continue; - - if (clamp_ttl) { - rr = dns_resource_record_ref(j->rr); - - r = dns_resource_record_clamp_ttl(&rr, LESS_BY(j->until, current) / USEC_PER_SEC); - if (r < 0) - return r; - } - - r = dns_answer_add(answer, rr ?: j->rr, j->ifindex, j->authenticated ? DNS_ANSWER_AUTHENTICATED : 0); - if (r < 0) - return r; - } - - c->n_hit++; - - *ret = answer; - *rcode = DNS_RCODE_SUCCESS; - *authenticated = have_authenticated && !have_non_authenticated; - answer = NULL; - - return n; -} - -int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address) { - DnsCacheItem *i, *first; - bool same_owner = true; - - assert(cache); - assert(rr); - - dns_cache_prune(cache); - - /* See if there's a cache entry for the same key. If there - * isn't there's no conflict */ - first = hashmap_get(cache->by_key, rr->key); - if (!first) - return 0; - - /* See if the RR key is owned by the same owner, if so, there - * isn't a conflict either */ - LIST_FOREACH(by_key, i, first) { - if (i->owner_family != owner_family || - !in_addr_equal(owner_family, &i->owner_address, owner_address)) { - same_owner = false; - break; - } - } - if (same_owner) - return 0; - - /* See if there's the exact same RR in the cache. If yes, then - * there's no conflict. */ - if (dns_cache_get(cache, rr)) - return 0; - - /* There's a conflict */ - return 1; -} - -int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p) { - unsigned ancount = 0; - Iterator iterator; - DnsCacheItem *i; - int r; - - assert(cache); - assert(p); - - HASHMAP_FOREACH(i, cache->by_key, iterator) { - DnsCacheItem *j; - - LIST_FOREACH(by_key, j, i) { - if (!j->rr) - continue; - - if (!j->shared_owner) - continue; - - r = dns_packet_append_rr(p, j->rr, NULL, NULL); - if (r == -EMSGSIZE && p->protocol == DNS_PROTOCOL_MDNS) { - /* For mDNS, if we're unable to stuff all known answers into the given packet, - * allocate a new one, push the RR into that one and link it to the current one. - */ - - DNS_PACKET_HEADER(p)->ancount = htobe16(ancount); - ancount = 0; - - r = dns_packet_new_query(&p->more, p->protocol, 0, true); - if (r < 0) - return r; - - /* continue with new packet */ - p = p->more; - r = dns_packet_append_rr(p, j->rr, NULL, NULL); - } - - if (r < 0) - return r; - - ancount++; - } - } - - DNS_PACKET_HEADER(p)->ancount = htobe16(ancount); - - return 0; -} - -void dns_cache_dump(DnsCache *cache, FILE *f) { - Iterator iterator; - DnsCacheItem *i; - - if (!cache) - return; - - if (!f) - f = stdout; - - HASHMAP_FOREACH(i, cache->by_key, iterator) { - DnsCacheItem *j; - - LIST_FOREACH(by_key, j, i) { - - fputc('\t', f); - - if (j->rr) { - const char *t; - t = dns_resource_record_to_string(j->rr); - if (!t) { - log_oom(); - continue; - } - - fputs(t, f); - fputc('\n', f); - } else { - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - - fputs(dns_resource_key_to_string(j->key, key_str, sizeof key_str), f); - fputs(" -- ", f); - fputs(j->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", f); - fputc('\n', f); - } - } - } -} - -bool dns_cache_is_empty(DnsCache *cache) { - if (!cache) - return true; - - return hashmap_isempty(cache->by_key); -} - -unsigned dns_cache_size(DnsCache *cache) { - if (!cache) - return 0; - - return hashmap_size(cache->by_key); -} diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h deleted file mode 100644 index 22a7c17377..0000000000 --- a/src/resolve/resolved-dns-cache.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include "hashmap.h" -#include "list.h" -#include "prioq.h" -#include "time-util.h" - -typedef struct DnsCache { - Hashmap *by_key; - Prioq *by_expiry; - unsigned n_hit; - unsigned n_miss; -} DnsCache; - -#include "resolved-dns-answer.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-question.h" -#include "resolved-dns-rr.h" - -void dns_cache_flush(DnsCache *c); -void dns_cache_prune(DnsCache *c); - -int dns_cache_put(DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, bool authenticated, uint32_t nsec_ttl, usec_t timestamp, int owner_family, const union in_addr_union *owner_address); -int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcode, DnsAnswer **answer, bool *authenticated); - -int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address); - -void dns_cache_dump(DnsCache *cache, FILE *f); -bool dns_cache_is_empty(DnsCache *cache); - -unsigned dns_cache_size(DnsCache *cache); - -int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p); diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c deleted file mode 100644 index d4a267c89f..0000000000 --- a/src/resolve/resolved-dns-dnssec.c +++ /dev/null @@ -1,2199 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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/>. -***/ - -#ifdef HAVE_GCRYPT -#include <gcrypt.h> -#endif - -#include "alloc-util.h" -#include "dns-domain.h" -#include "gcrypt-util.h" -#include "hexdecoct.h" -#include "resolved-dns-dnssec.h" -#include "resolved-dns-packet.h" -#include "string-table.h" - -#define VERIFY_RRS_MAX 256 -#define MAX_KEY_SIZE (32*1024) - -/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */ -#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE) - -/* Maximum number of NSEC3 iterations we'll do. RFC5155 says 2500 shall be the maximum useful value */ -#define NSEC3_ITERATIONS_MAX 2500 - -/* - * The DNSSEC Chain of trust: - * - * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone - * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree - * DS RRs are protected like normal RRs - * - * Example chain: - * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS - */ - -uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke) { - const uint8_t *p; - uint32_t sum, f; - size_t i; - - /* The algorithm from RFC 4034, Appendix B. */ - - assert(dnskey); - assert(dnskey->key->type == DNS_TYPE_DNSKEY); - - f = (uint32_t) dnskey->dnskey.flags; - - if (mask_revoke) - f &= ~DNSKEY_FLAG_REVOKE; - - sum = f + ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm); - - p = dnskey->dnskey.key; - - for (i = 0; i < dnskey->dnskey.key_size; i++) - sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i]; - - sum += (sum >> 16) & UINT32_C(0xFFFF); - - return sum & UINT32_C(0xFFFF); -} - -int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) { - size_t c = 0; - int r; - - /* Converts the specified hostname into DNSSEC canonicalized - * form. */ - - if (buffer_max < 2) - return -ENOBUFS; - - for (;;) { - r = dns_label_unescape(&n, buffer, buffer_max); - if (r < 0) - return r; - if (r == 0) - break; - - if (buffer_max < (size_t) r + 2) - return -ENOBUFS; - - /* The DNSSEC canonical form is not clear on what to - * do with dots appearing in labels, the way DNS-SD - * does it. Refuse it for now. */ - - if (memchr(buffer, '.', r)) - return -EINVAL; - - ascii_strlower_n(buffer, (size_t) r); - buffer[r] = '.'; - - buffer += r + 1; - c += r + 1; - - buffer_max -= r + 1; - } - - if (c <= 0) { - /* Not even a single label: this is the root domain name */ - - assert(buffer_max > 2); - buffer[0] = '.'; - buffer[1] = 0; - - return 1; - } - - return (int) c; -} - -#ifdef HAVE_GCRYPT - -static int rr_compare(const void *a, const void *b) { - DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b; - size_t m; - int r; - - /* Let's order the RRs according to RFC 4034, Section 6.3 */ - - assert(x); - assert(*x); - assert((*x)->wire_format); - assert(y); - assert(*y); - assert((*y)->wire_format); - - m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x), DNS_RESOURCE_RECORD_RDATA_SIZE(*y)); - - r = memcmp(DNS_RESOURCE_RECORD_RDATA(*x), DNS_RESOURCE_RECORD_RDATA(*y), m); - if (r != 0) - return r; - - if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) - return -1; - else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) - return 1; - - return 0; -} - -static int dnssec_rsa_verify_raw( - const char *hash_algorithm, - const void *signature, size_t signature_size, - const void *data, size_t data_size, - const void *exponent, size_t exponent_size, - const void *modulus, size_t modulus_size) { - - gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL; - gcry_mpi_t n = NULL, e = NULL, s = NULL; - gcry_error_t ge; - int r; - - assert(hash_algorithm); - - ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL); - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL); - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL); - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_sexp_build(&signature_sexp, - NULL, - "(sig-val (rsa (s %m)))", - s); - - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_sexp_build(&data_sexp, - NULL, - "(data (flags pkcs1) (hash %s %b))", - hash_algorithm, - (int) data_size, - data); - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_sexp_build(&public_key_sexp, - NULL, - "(public-key (rsa (n %m) (e %m)))", - n, - e); - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp); - if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE) - r = 0; - else if (ge != 0) { - log_debug("RSA signature check failed: %s", gpg_strerror(ge)); - r = -EIO; - } else - r = 1; - -finish: - if (e) - gcry_mpi_release(e); - if (n) - gcry_mpi_release(n); - if (s) - gcry_mpi_release(s); - - if (public_key_sexp) - gcry_sexp_release(public_key_sexp); - if (signature_sexp) - gcry_sexp_release(signature_sexp); - if (data_sexp) - gcry_sexp_release(data_sexp); - - return r; -} - -static int dnssec_rsa_verify( - const char *hash_algorithm, - const void *hash, size_t hash_size, - DnsResourceRecord *rrsig, - DnsResourceRecord *dnskey) { - - size_t exponent_size, modulus_size; - void *exponent, *modulus; - - assert(hash_algorithm); - assert(hash); - assert(hash_size > 0); - assert(rrsig); - assert(dnskey); - - if (*(uint8_t*) dnskey->dnskey.key == 0) { - /* exponent is > 255 bytes long */ - - exponent = (uint8_t*) dnskey->dnskey.key + 3; - exponent_size = - ((size_t) (((uint8_t*) dnskey->dnskey.key)[1]) << 8) | - ((size_t) ((uint8_t*) dnskey->dnskey.key)[2]); - - if (exponent_size < 256) - return -EINVAL; - - if (3 + exponent_size >= dnskey->dnskey.key_size) - return -EINVAL; - - modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size; - modulus_size = dnskey->dnskey.key_size - 3 - exponent_size; - - } else { - /* exponent is <= 255 bytes long */ - - exponent = (uint8_t*) dnskey->dnskey.key + 1; - exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0]; - - if (exponent_size <= 0) - return -EINVAL; - - if (1 + exponent_size >= dnskey->dnskey.key_size) - return -EINVAL; - - modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size; - modulus_size = dnskey->dnskey.key_size - 1 - exponent_size; - } - - return dnssec_rsa_verify_raw( - hash_algorithm, - rrsig->rrsig.signature, rrsig->rrsig.signature_size, - hash, hash_size, - exponent, exponent_size, - modulus, modulus_size); -} - -static int dnssec_ecdsa_verify_raw( - const char *hash_algorithm, - const char *curve, - const void *signature_r, size_t signature_r_size, - const void *signature_s, size_t signature_s_size, - const void *data, size_t data_size, - const void *key, size_t key_size) { - - gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL; - gcry_mpi_t q = NULL, r = NULL, s = NULL; - gcry_error_t ge; - int k; - - assert(hash_algorithm); - - ge = gcry_mpi_scan(&r, GCRYMPI_FMT_USG, signature_r, signature_r_size, NULL); - if (ge != 0) { - k = -EIO; - goto finish; - } - - ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature_s, signature_s_size, NULL); - if (ge != 0) { - k = -EIO; - goto finish; - } - - ge = gcry_mpi_scan(&q, GCRYMPI_FMT_USG, key, key_size, NULL); - if (ge != 0) { - k = -EIO; - goto finish; - } - - ge = gcry_sexp_build(&signature_sexp, - NULL, - "(sig-val (ecdsa (r %m) (s %m)))", - r, - s); - if (ge != 0) { - k = -EIO; - goto finish; - } - - ge = gcry_sexp_build(&data_sexp, - NULL, - "(data (flags rfc6979) (hash %s %b))", - hash_algorithm, - (int) data_size, - data); - if (ge != 0) { - k = -EIO; - goto finish; - } - - ge = gcry_sexp_build(&public_key_sexp, - NULL, - "(public-key (ecc (curve %s) (q %m)))", - curve, - q); - if (ge != 0) { - k = -EIO; - goto finish; - } - - ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp); - if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE) - k = 0; - else if (ge != 0) { - log_debug("ECDSA signature check failed: %s", gpg_strerror(ge)); - k = -EIO; - } else - k = 1; -finish: - if (r) - gcry_mpi_release(r); - if (s) - gcry_mpi_release(s); - if (q) - gcry_mpi_release(q); - - if (public_key_sexp) - gcry_sexp_release(public_key_sexp); - if (signature_sexp) - gcry_sexp_release(signature_sexp); - if (data_sexp) - gcry_sexp_release(data_sexp); - - return k; -} - -static int dnssec_ecdsa_verify( - const char *hash_algorithm, - int algorithm, - const void *hash, size_t hash_size, - DnsResourceRecord *rrsig, - DnsResourceRecord *dnskey) { - - const char *curve; - size_t key_size; - uint8_t *q; - - assert(hash); - assert(hash_size); - assert(rrsig); - assert(dnskey); - - if (algorithm == DNSSEC_ALGORITHM_ECDSAP256SHA256) { - key_size = 32; - curve = "NIST P-256"; - } else if (algorithm == DNSSEC_ALGORITHM_ECDSAP384SHA384) { - key_size = 48; - curve = "NIST P-384"; - } else - return -EOPNOTSUPP; - - if (dnskey->dnskey.key_size != key_size * 2) - return -EINVAL; - - if (rrsig->rrsig.signature_size != key_size * 2) - return -EINVAL; - - q = alloca(key_size*2 + 1); - q[0] = 0x04; /* Prepend 0x04 to indicate an uncompressed key */ - memcpy(q+1, dnskey->dnskey.key, key_size*2); - - return dnssec_ecdsa_verify_raw( - hash_algorithm, - curve, - rrsig->rrsig.signature, key_size, - (uint8_t*) rrsig->rrsig.signature + key_size, key_size, - hash, hash_size, - q, key_size*2+1); -} - -static void md_add_uint8(gcry_md_hd_t md, uint8_t v) { - gcry_md_write(md, &v, sizeof(v)); -} - -static void md_add_uint16(gcry_md_hd_t md, uint16_t v) { - v = htobe16(v); - gcry_md_write(md, &v, sizeof(v)); -} - -static void md_add_uint32(gcry_md_hd_t md, uint32_t v) { - v = htobe32(v); - gcry_md_write(md, &v, sizeof(v)); -} - -static int dnssec_rrsig_prepare(DnsResourceRecord *rrsig) { - int n_key_labels, n_signer_labels; - const char *name; - int r; - - /* Checks whether the specified RRSIG RR is somewhat valid, and initializes the .n_skip_labels_source and - * .n_skip_labels_signer fields so that we can use them later on. */ - - assert(rrsig); - assert(rrsig->key->type == DNS_TYPE_RRSIG); - - /* Check if this RRSIG RR is already prepared */ - if (rrsig->n_skip_labels_source != (unsigned) -1) - return 0; - - if (rrsig->rrsig.inception > rrsig->rrsig.expiration) - return -EINVAL; - - name = dns_resource_key_name(rrsig->key); - - n_key_labels = dns_name_count_labels(name); - if (n_key_labels < 0) - return n_key_labels; - if (rrsig->rrsig.labels > n_key_labels) - return -EINVAL; - - n_signer_labels = dns_name_count_labels(rrsig->rrsig.signer); - if (n_signer_labels < 0) - return n_signer_labels; - if (n_signer_labels > rrsig->rrsig.labels) - return -EINVAL; - - r = dns_name_skip(name, n_key_labels - n_signer_labels, &name); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - /* Check if the signer is really a suffix of us */ - r = dns_name_equal(name, rrsig->rrsig.signer); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - rrsig->n_skip_labels_source = n_key_labels - rrsig->rrsig.labels; - rrsig->n_skip_labels_signer = n_key_labels - n_signer_labels; - - return 0; -} - -static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) { - usec_t expiration, inception, skew; - - assert(rrsig); - assert(rrsig->key->type == DNS_TYPE_RRSIG); - - if (realtime == USEC_INFINITY) - realtime = now(CLOCK_REALTIME); - - expiration = rrsig->rrsig.expiration * USEC_PER_SEC; - inception = rrsig->rrsig.inception * USEC_PER_SEC; - - /* Consider inverted validity intervals as expired */ - if (inception > expiration) - return true; - - /* Permit a certain amount of clock skew of 10% of the valid - * time range. This takes inspiration from unbound's - * resolver. */ - skew = (expiration - inception) / 10; - if (skew > SKEW_MAX) - skew = SKEW_MAX; - - if (inception < skew) - inception = 0; - else - inception -= skew; - - if (expiration + skew < expiration) - expiration = USEC_INFINITY; - else - expiration += skew; - - return realtime < inception || realtime > expiration; -} - -static int algorithm_to_gcrypt_md(uint8_t algorithm) { - - /* Translates a DNSSEC signature algorithm into a gcrypt - * digest identifier. - * - * Note that we implement all algorithms listed as "Must - * implement" and "Recommended to Implement" in RFC6944. We - * don't implement any algorithms that are listed as - * "Optional" or "Must Not Implement". Specifically, we do not - * implement RSAMD5, DSASHA1, DH, DSA-NSEC3-SHA1, and - * GOST-ECC. */ - - switch (algorithm) { - - case DNSSEC_ALGORITHM_RSASHA1: - case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: - return GCRY_MD_SHA1; - - case DNSSEC_ALGORITHM_RSASHA256: - case DNSSEC_ALGORITHM_ECDSAP256SHA256: - return GCRY_MD_SHA256; - - case DNSSEC_ALGORITHM_ECDSAP384SHA384: - return GCRY_MD_SHA384; - - case DNSSEC_ALGORITHM_RSASHA512: - return GCRY_MD_SHA512; - - default: - return -EOPNOTSUPP; - } -} - -static void dnssec_fix_rrset_ttl( - DnsResourceRecord *list[], - unsigned n, - DnsResourceRecord *rrsig, - usec_t realtime) { - - unsigned k; - - assert(list); - assert(n > 0); - assert(rrsig); - - for (k = 0; k < n; k++) { - DnsResourceRecord *rr = list[k]; - - /* Pick the TTL as the minimum of the RR's TTL, the - * RR's original TTL according to the RRSIG and the - * RRSIG's own TTL, see RFC 4035, Section 5.3.3 */ - rr->ttl = MIN3(rr->ttl, rrsig->rrsig.original_ttl, rrsig->ttl); - rr->expiry = rrsig->rrsig.expiration * USEC_PER_SEC; - - /* Copy over information about the signer and wildcard source of synthesis */ - rr->n_skip_labels_source = rrsig->n_skip_labels_source; - rr->n_skip_labels_signer = rrsig->n_skip_labels_signer; - } - - rrsig->expiry = rrsig->rrsig.expiration * USEC_PER_SEC; -} - -int dnssec_verify_rrset( - DnsAnswer *a, - const DnsResourceKey *key, - DnsResourceRecord *rrsig, - DnsResourceRecord *dnskey, - usec_t realtime, - DnssecResult *result) { - - uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX]; - DnsResourceRecord **list, *rr; - const char *source, *name; - gcry_md_hd_t md = NULL; - int r, md_algorithm; - size_t k, n = 0; - size_t hash_size; - void *hash; - bool wildcard; - - assert(key); - assert(rrsig); - assert(dnskey); - assert(result); - assert(rrsig->key->type == DNS_TYPE_RRSIG); - assert(dnskey->key->type == DNS_TYPE_DNSKEY); - - /* Verifies that the RRSet matches the specified "key" in "a", - * using the signature "rrsig" and the key "dnskey". It's - * assumed that RRSIG and DNSKEY match. */ - - md_algorithm = algorithm_to_gcrypt_md(rrsig->rrsig.algorithm); - if (md_algorithm == -EOPNOTSUPP) { - *result = DNSSEC_UNSUPPORTED_ALGORITHM; - return 0; - } - if (md_algorithm < 0) - return md_algorithm; - - r = dnssec_rrsig_prepare(rrsig); - if (r == -EINVAL) { - *result = DNSSEC_INVALID; - return r; - } - if (r < 0) - return r; - - r = dnssec_rrsig_expired(rrsig, realtime); - if (r < 0) - return r; - if (r > 0) { - *result = DNSSEC_SIGNATURE_EXPIRED; - return 0; - } - - name = dns_resource_key_name(key); - - /* Some keys may only appear signed in the zone apex, and are invalid anywhere else. (SOA, NS...) */ - if (dns_type_apex_only(rrsig->rrsig.type_covered)) { - r = dns_name_equal(rrsig->rrsig.signer, name); - if (r < 0) - return r; - if (r == 0) { - *result = DNSSEC_INVALID; - return 0; - } - } - - /* OTOH DS RRs may not appear in the zone apex, but are valid everywhere else. */ - if (rrsig->rrsig.type_covered == DNS_TYPE_DS) { - r = dns_name_equal(rrsig->rrsig.signer, name); - if (r < 0) - return r; - if (r > 0) { - *result = DNSSEC_INVALID; - return 0; - } - } - - /* Determine the "Source of Synthesis" and whether this is a wildcard RRSIG */ - r = dns_name_suffix(name, rrsig->rrsig.labels, &source); - if (r < 0) - return r; - if (r > 0 && !dns_type_may_wildcard(rrsig->rrsig.type_covered)) { - /* We refuse to validate NSEC3 or SOA RRs that are synthesized from wildcards */ - *result = DNSSEC_INVALID; - return 0; - } - if (r == 1) { - /* If we stripped a single label, then let's see if that maybe was "*". If so, we are not really - * synthesized from a wildcard, we are the wildcard itself. Treat that like a normal name. */ - r = dns_name_startswith(name, "*"); - if (r < 0) - return r; - if (r > 0) - source = name; - - wildcard = r == 0; - } else - wildcard = r > 0; - - /* Collect all relevant RRs in a single array, so that we can look at the RRset */ - list = newa(DnsResourceRecord *, dns_answer_size(a)); - - DNS_ANSWER_FOREACH(rr, a) { - r = dns_resource_key_equal(key, rr->key); - if (r < 0) - return r; - if (r == 0) - continue; - - /* We need the wire format for ordering, and digest calculation */ - r = dns_resource_record_to_wire_format(rr, true); - if (r < 0) - return r; - - list[n++] = rr; - - if (n > VERIFY_RRS_MAX) - return -E2BIG; - } - - if (n <= 0) - return -ENODATA; - - /* Bring the RRs into canonical order */ - qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare); - - /* OK, the RRs are now in canonical order. Let's calculate the digest */ - initialize_libgcrypt(false); - - hash_size = gcry_md_get_algo_dlen(md_algorithm); - assert(hash_size > 0); - - gcry_md_open(&md, md_algorithm, 0); - if (!md) - return -EIO; - - md_add_uint16(md, rrsig->rrsig.type_covered); - md_add_uint8(md, rrsig->rrsig.algorithm); - md_add_uint8(md, rrsig->rrsig.labels); - md_add_uint32(md, rrsig->rrsig.original_ttl); - md_add_uint32(md, rrsig->rrsig.expiration); - md_add_uint32(md, rrsig->rrsig.inception); - md_add_uint16(md, rrsig->rrsig.key_tag); - - r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true); - if (r < 0) - goto finish; - gcry_md_write(md, wire_format_name, r); - - /* Convert the source of synthesis into wire format */ - r = dns_name_to_wire_format(source, wire_format_name, sizeof(wire_format_name), true); - if (r < 0) - goto finish; - - for (k = 0; k < n; k++) { - size_t l; - - rr = list[k]; - - /* Hash the source of synthesis. If this is a wildcard, then prefix it with the *. label */ - if (wildcard) - gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2); - gcry_md_write(md, wire_format_name, r); - - md_add_uint16(md, rr->key->type); - md_add_uint16(md, rr->key->class); - md_add_uint32(md, rrsig->rrsig.original_ttl); - - l = DNS_RESOURCE_RECORD_RDATA_SIZE(rr); - assert(l <= 0xFFFF); - - md_add_uint16(md, (uint16_t) l); - gcry_md_write(md, DNS_RESOURCE_RECORD_RDATA(rr), l); - } - - hash = gcry_md_read(md, 0); - if (!hash) { - r = -EIO; - goto finish; - } - - switch (rrsig->rrsig.algorithm) { - - case DNSSEC_ALGORITHM_RSASHA1: - case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: - case DNSSEC_ALGORITHM_RSASHA256: - case DNSSEC_ALGORITHM_RSASHA512: - r = dnssec_rsa_verify( - gcry_md_algo_name(md_algorithm), - hash, hash_size, - rrsig, - dnskey); - break; - - case DNSSEC_ALGORITHM_ECDSAP256SHA256: - case DNSSEC_ALGORITHM_ECDSAP384SHA384: - r = dnssec_ecdsa_verify( - gcry_md_algo_name(md_algorithm), - rrsig->rrsig.algorithm, - hash, hash_size, - rrsig, - dnskey); - break; - } - - if (r < 0) - goto finish; - - /* Now, fix the ttl, expiry, and remember the synthesizing source and the signer */ - if (r > 0) - dnssec_fix_rrset_ttl(list, n, rrsig, realtime); - - if (r == 0) - *result = DNSSEC_INVALID; - else if (wildcard) - *result = DNSSEC_VALIDATED_WILDCARD; - else - *result = DNSSEC_VALIDATED; - - r = 0; - -finish: - gcry_md_close(md); - return r; -} - -int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) { - - assert(rrsig); - assert(dnskey); - - /* Checks if the specified DNSKEY RR matches the key used for - * the signature in the specified RRSIG RR */ - - if (rrsig->key->type != DNS_TYPE_RRSIG) - return -EINVAL; - - if (dnskey->key->type != DNS_TYPE_DNSKEY) - return 0; - if (dnskey->key->class != rrsig->key->class) - return 0; - if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0) - return 0; - if (!revoked_ok && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE)) - return 0; - if (dnskey->dnskey.protocol != 3) - return 0; - if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm) - return 0; - - if (dnssec_keytag(dnskey, false) != rrsig->rrsig.key_tag) - return 0; - - return dns_name_equal(dns_resource_key_name(dnskey->key), rrsig->rrsig.signer); -} - -int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) { - assert(key); - assert(rrsig); - - /* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */ - - if (rrsig->key->type != DNS_TYPE_RRSIG) - return 0; - if (rrsig->key->class != key->class) - return 0; - if (rrsig->rrsig.type_covered != key->type) - return 0; - - return dns_name_equal(dns_resource_key_name(rrsig->key), dns_resource_key_name(key)); -} - -int dnssec_verify_rrset_search( - DnsAnswer *a, - const DnsResourceKey *key, - DnsAnswer *validated_dnskeys, - usec_t realtime, - DnssecResult *result, - DnsResourceRecord **ret_rrsig) { - - bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false; - DnsResourceRecord *rrsig; - int r; - - assert(key); - assert(result); - - /* Verifies all RRs from "a" that match the key "key" against DNSKEYs in "validated_dnskeys" */ - - if (!a || a->n_rrs <= 0) - return -ENODATA; - - /* Iterate through each RRSIG RR. */ - DNS_ANSWER_FOREACH(rrsig, a) { - DnsResourceRecord *dnskey; - DnsAnswerFlags flags; - - /* Is this an RRSIG RR that applies to RRs matching our key? */ - r = dnssec_key_match_rrsig(key, rrsig); - if (r < 0) - return r; - if (r == 0) - continue; - - found_rrsig = true; - - /* Look for a matching key */ - DNS_ANSWER_FOREACH_FLAGS(dnskey, flags, validated_dnskeys) { - DnssecResult one_result; - - if ((flags & DNS_ANSWER_AUTHENTICATED) == 0) - continue; - - /* Is this a DNSKEY RR that matches they key of our RRSIG? */ - r = dnssec_rrsig_match_dnskey(rrsig, dnskey, false); - if (r < 0) - return r; - if (r == 0) - continue; - - /* Take the time here, if it isn't set yet, so - * that we do all validations with the same - * time. */ - if (realtime == USEC_INFINITY) - realtime = now(CLOCK_REALTIME); - - /* Yay, we found a matching RRSIG with a matching - * DNSKEY, awesome. Now let's verify all entries of - * the RRSet against the RRSIG and DNSKEY - * combination. */ - - r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime, &one_result); - if (r < 0) - return r; - - switch (one_result) { - - case DNSSEC_VALIDATED: - case DNSSEC_VALIDATED_WILDCARD: - /* Yay, the RR has been validated, - * return immediately, but fix up the expiry */ - if (ret_rrsig) - *ret_rrsig = rrsig; - - *result = one_result; - return 0; - - case DNSSEC_INVALID: - /* If the signature is invalid, let's try another - key and/or signature. After all they - key_tags and stuff are not unique, and - might be shared by multiple keys. */ - found_invalid = true; - continue; - - case DNSSEC_UNSUPPORTED_ALGORITHM: - /* If the key algorithm is - unsupported, try another - RRSIG/DNSKEY pair, but remember we - encountered this, so that we can - return a proper error when we - encounter nothing better. */ - found_unsupported_algorithm = true; - continue; - - case DNSSEC_SIGNATURE_EXPIRED: - /* If the signature is expired, try - another one, but remember it, so - that we can return this */ - found_expired_rrsig = true; - continue; - - default: - assert_not_reached("Unexpected DNSSEC validation result"); - } - } - } - - if (found_expired_rrsig) - *result = DNSSEC_SIGNATURE_EXPIRED; - else if (found_unsupported_algorithm) - *result = DNSSEC_UNSUPPORTED_ALGORITHM; - else if (found_invalid) - *result = DNSSEC_INVALID; - else if (found_rrsig) - *result = DNSSEC_MISSING_KEY; - else - *result = DNSSEC_NO_SIGNATURE; - - if (ret_rrsig) - *ret_rrsig = NULL; - - return 0; -} - -int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) { - DnsResourceRecord *rr; - int r; - - /* Checks whether there's at least one RRSIG in 'a' that proctects RRs of the specified key */ - - DNS_ANSWER_FOREACH(rr, a) { - r = dnssec_key_match_rrsig(key, rr); - if (r < 0) - return r; - if (r > 0) - return 1; - } - - return 0; -} - -static int digest_to_gcrypt_md(uint8_t algorithm) { - - /* Translates a DNSSEC digest algorithm into a gcrypt digest identifier */ - - switch (algorithm) { - - case DNSSEC_DIGEST_SHA1: - return GCRY_MD_SHA1; - - case DNSSEC_DIGEST_SHA256: - return GCRY_MD_SHA256; - - case DNSSEC_DIGEST_SHA384: - return GCRY_MD_SHA384; - - default: - return -EOPNOTSUPP; - } -} - -int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) { - char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX]; - gcry_md_hd_t md = NULL; - size_t hash_size; - int md_algorithm, r; - void *result; - - assert(dnskey); - assert(ds); - - /* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */ - - if (dnskey->key->type != DNS_TYPE_DNSKEY) - return -EINVAL; - if (ds->key->type != DNS_TYPE_DS) - return -EINVAL; - if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0) - return -EKEYREJECTED; - if (!mask_revoke && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE)) - return -EKEYREJECTED; - if (dnskey->dnskey.protocol != 3) - return -EKEYREJECTED; - - if (dnskey->dnskey.algorithm != ds->ds.algorithm) - return 0; - if (dnssec_keytag(dnskey, mask_revoke) != ds->ds.key_tag) - return 0; - - initialize_libgcrypt(false); - - md_algorithm = digest_to_gcrypt_md(ds->ds.digest_type); - if (md_algorithm < 0) - return md_algorithm; - - hash_size = gcry_md_get_algo_dlen(md_algorithm); - assert(hash_size > 0); - - if (ds->ds.digest_size != hash_size) - return 0; - - r = dnssec_canonicalize(dns_resource_key_name(dnskey->key), owner_name, sizeof(owner_name)); - if (r < 0) - return r; - - gcry_md_open(&md, md_algorithm, 0); - if (!md) - return -EIO; - - gcry_md_write(md, owner_name, r); - if (mask_revoke) - md_add_uint16(md, dnskey->dnskey.flags & ~DNSKEY_FLAG_REVOKE); - else - md_add_uint16(md, dnskey->dnskey.flags); - md_add_uint8(md, dnskey->dnskey.protocol); - md_add_uint8(md, dnskey->dnskey.algorithm); - gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size); - - result = gcry_md_read(md, 0); - if (!result) { - r = -EIO; - goto finish; - } - - r = memcmp(result, ds->ds.digest, ds->ds.digest_size) != 0; - -finish: - gcry_md_close(md); - return r; -} - -int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) { - DnsResourceRecord *ds; - DnsAnswerFlags flags; - int r; - - assert(dnskey); - - if (dnskey->key->type != DNS_TYPE_DNSKEY) - return 0; - - DNS_ANSWER_FOREACH_FLAGS(ds, flags, validated_ds) { - - if ((flags & DNS_ANSWER_AUTHENTICATED) == 0) - continue; - - if (ds->key->type != DNS_TYPE_DS) - continue; - if (ds->key->class != dnskey->key->class) - continue; - - r = dns_name_equal(dns_resource_key_name(dnskey->key), dns_resource_key_name(ds->key)); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dnssec_verify_dnskey_by_ds(dnskey, ds, false); - if (IN_SET(r, -EKEYREJECTED, -EOPNOTSUPP)) - return 0; /* The DNSKEY is revoked or otherwise invalid, or we don't support the digest algorithm */ - if (r < 0) - return r; - if (r > 0) - return 1; - } - - return 0; -} - -static int nsec3_hash_to_gcrypt_md(uint8_t algorithm) { - - /* Translates a DNSSEC NSEC3 hash algorithm into a gcrypt digest identifier */ - - switch (algorithm) { - - case NSEC3_ALGORITHM_SHA1: - return GCRY_MD_SHA1; - - default: - return -EOPNOTSUPP; - } -} - -int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { - uint8_t wire_format[DNS_WIRE_FOMAT_HOSTNAME_MAX]; - gcry_md_hd_t md = NULL; - size_t hash_size; - int algorithm; - void *result; - unsigned k; - int r; - - assert(nsec3); - assert(name); - assert(ret); - - if (nsec3->key->type != DNS_TYPE_NSEC3) - return -EINVAL; - - if (nsec3->nsec3.iterations > NSEC3_ITERATIONS_MAX) { - log_debug("Ignoring NSEC3 RR %s with excessive number of iterations.", dns_resource_record_to_string(nsec3)); - return -EOPNOTSUPP; - } - - algorithm = nsec3_hash_to_gcrypt_md(nsec3->nsec3.algorithm); - if (algorithm < 0) - return algorithm; - - initialize_libgcrypt(false); - - hash_size = gcry_md_get_algo_dlen(algorithm); - assert(hash_size > 0); - - if (nsec3->nsec3.next_hashed_name_size != hash_size) - return -EINVAL; - - r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true); - if (r < 0) - return r; - - gcry_md_open(&md, algorithm, 0); - if (!md) - return -EIO; - - gcry_md_write(md, wire_format, r); - gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size); - - result = gcry_md_read(md, 0); - if (!result) { - r = -EIO; - goto finish; - } - - for (k = 0; k < nsec3->nsec3.iterations; k++) { - uint8_t tmp[hash_size]; - memcpy(tmp, result, hash_size); - - gcry_md_reset(md); - gcry_md_write(md, tmp, hash_size); - gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size); - - result = gcry_md_read(md, 0); - if (!result) { - r = -EIO; - goto finish; - } - } - - memcpy(ret, result, hash_size); - r = (int) hash_size; - -finish: - gcry_md_close(md); - return r; -} - -static int nsec3_is_good(DnsResourceRecord *rr, DnsResourceRecord *nsec3) { - const char *a, *b; - int r; - - assert(rr); - - if (rr->key->type != DNS_TYPE_NSEC3) - return 0; - - /* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */ - if (!IN_SET(rr->nsec3.flags, 0, 1)) - return 0; - - /* Ignore NSEC3 RRs whose algorithm we don't know */ - if (nsec3_hash_to_gcrypt_md(rr->nsec3.algorithm) < 0) - return 0; - /* Ignore NSEC3 RRs with an excessive number of required iterations */ - if (rr->nsec3.iterations > NSEC3_ITERATIONS_MAX) - return 0; - - /* 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 && rr->n_skip_labels_signer != (unsigned) -1) - return 0; - - if (!nsec3) - return 1; - - /* If a second NSEC3 RR is specified, also check if they are from the same zone. */ - - if (nsec3 == rr) /* Shortcut */ - return 1; - - if (rr->key->class != nsec3->key->class) - return 0; - if (rr->nsec3.algorithm != nsec3->nsec3.algorithm) - return 0; - if (rr->nsec3.iterations != nsec3->nsec3.iterations) - return 0; - if (rr->nsec3.salt_size != nsec3->nsec3.salt_size) - return 0; - if (memcmp(rr->nsec3.salt, nsec3->nsec3.salt, rr->nsec3.salt_size) != 0) - return 0; - - a = dns_resource_key_name(rr->key); - r = dns_name_parent(&a); /* strip off hash */ - if (r < 0) - return r; - if (r == 0) - return 0; - - b = dns_resource_key_name(nsec3->key); - r = dns_name_parent(&b); /* strip off hash */ - if (r < 0) - return r; - if (r == 0) - return 0; - - /* Make sure both have the same parent */ - return dns_name_equal(a, b); -} - -static int nsec3_hashed_domain_format(const uint8_t *hashed, size_t hashed_size, const char *zone, char **ret) { - _cleanup_free_ char *l = NULL; - char *j; - - assert(hashed); - assert(hashed_size > 0); - assert(zone); - assert(ret); - - l = base32hexmem(hashed, hashed_size, false); - if (!l) - return -ENOMEM; - - j = strjoin(l, ".", zone, NULL); - if (!j) - return -ENOMEM; - - *ret = j; - return (int) hashed_size; -} - -static int nsec3_hashed_domain_make(DnsResourceRecord *nsec3, const char *domain, const char *zone, char **ret) { - uint8_t hashed[DNSSEC_HASH_SIZE_MAX]; - int hashed_size; - - assert(nsec3); - assert(domain); - assert(zone); - assert(ret); - - hashed_size = dnssec_nsec3_hash(nsec3, domain, hashed); - if (hashed_size < 0) - return hashed_size; - - return nsec3_hashed_domain_format(hashed, (size_t) hashed_size, zone, ret); -} - -/* See RFC 5155, Section 8 - * First try to find a NSEC3 record that matches our query precisely, if that fails, find the closest - * enclosure. Secondly, find a proof that there is no closer enclosure and either a proof that there - * is no wildcard domain as a direct descendant of the closest enclosure, or find an NSEC3 record that - * matches the wildcard domain. - * - * Based on this we can prove either the existence of the record in @key, or NXDOMAIN or NODATA, or - * that there is no proof either way. The latter is the case if a the proof of non-existence of a given - * name uses an NSEC3 record with the opt-out bit set. Lastly, if we are given insufficient NSEC3 records - * to conclude anything we indicate this by returning NO_RR. */ -static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { - _cleanup_free_ char *next_closer_domain = NULL, *wildcard_domain = NULL; - const char *zone, *p, *pp = NULL, *wildcard; - DnsResourceRecord *rr, *enclosure_rr, *zone_rr, *wildcard_rr = NULL; - DnsAnswerFlags flags; - int hashed_size, r; - bool a, no_closer = false, no_wildcard = false, optout = false; - - assert(key); - assert(result); - - /* First step, find the zone name and the NSEC3 parameters of the zone. - * it is sufficient to look for the longest common suffix we find with - * any NSEC3 RR in the response. Any NSEC3 record will do as all NSEC3 - * records from a given zone in a response must use the same - * parameters. */ - zone = dns_resource_key_name(key); - for (;;) { - DNS_ANSWER_FOREACH_FLAGS(zone_rr, flags, answer) { - r = nsec3_is_good(zone_rr, NULL); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dns_name_equal_skip(dns_resource_key_name(zone_rr->key), 1, zone); - if (r < 0) - return r; - if (r > 0) - goto found_zone; - } - - /* Strip one label from the front */ - r = dns_name_parent(&zone); - if (r < 0) - return r; - if (r == 0) - break; - } - - *result = DNSSEC_NSEC_NO_RR; - return 0; - -found_zone: - /* Second step, find the closest encloser NSEC3 RR in 'answer' that matches 'key' */ - p = dns_resource_key_name(key); - for (;;) { - _cleanup_free_ char *hashed_domain = NULL; - - hashed_size = nsec3_hashed_domain_make(zone_rr, p, zone, &hashed_domain); - if (hashed_size == -EOPNOTSUPP) { - *result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM; - return 0; - } - if (hashed_size < 0) - return hashed_size; - - DNS_ANSWER_FOREACH_FLAGS(enclosure_rr, flags, answer) { - - r = nsec3_is_good(enclosure_rr, zone_rr); - if (r < 0) - return r; - if (r == 0) - continue; - - if (enclosure_rr->nsec3.next_hashed_name_size != (size_t) hashed_size) - continue; - - r = dns_name_equal(dns_resource_key_name(enclosure_rr->key), hashed_domain); - if (r < 0) - return r; - if (r > 0) { - a = flags & DNS_ANSWER_AUTHENTICATED; - goto found_closest_encloser; - } - } - - /* We didn't find the closest encloser with this name, - * but let's remember this domain name, it might be - * the next closer name */ - - pp = p; - - /* Strip one label from the front */ - r = dns_name_parent(&p); - if (r < 0) - return r; - if (r == 0) - break; - } - - *result = DNSSEC_NSEC_NO_RR; - return 0; - -found_closest_encloser: - /* We found a closest encloser in 'p'; next closer is 'pp' */ - - 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; - else if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_CNAME)) - *result = DNSSEC_NSEC_CNAME; - else - *result = DNSSEC_NSEC_NODATA; - - if (authenticated) - *authenticated = a; - if (ttl) - *ttl = enclosure_rr->ttl; - - return 0; - } - - /* 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); - r = nsec3_hashed_domain_make(enclosure_rr, wildcard, zone, &wildcard_domain); - if (r < 0) - return r; - if (r != hashed_size) - return -EBADMSG; - - r = nsec3_hashed_domain_make(enclosure_rr, pp, zone, &next_closer_domain); - if (r < 0) - return r; - if (r != hashed_size) - return -EBADMSG; - - DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { - _cleanup_free_ char *next_hashed_domain = NULL; - - r = nsec3_is_good(rr, zone_rr); - if (r < 0) - return r; - if (r == 0) - continue; - - r = nsec3_hashed_domain_format(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, zone, &next_hashed_domain); - if (r < 0) - return r; - - r = dns_name_between(dns_resource_key_name(rr->key), next_closer_domain, next_hashed_domain); - if (r < 0) - return r; - if (r > 0) { - if (rr->nsec3.flags & 1) - optout = true; - - a = a && (flags & DNS_ANSWER_AUTHENTICATED); - - no_closer = true; - } - - r = dns_name_equal(dns_resource_key_name(rr->key), wildcard_domain); - if (r < 0) - return r; - if (r > 0) { - a = a && (flags & DNS_ANSWER_AUTHENTICATED); - - wildcard_rr = rr; - } - - r = dns_name_between(dns_resource_key_name(rr->key), wildcard_domain, next_hashed_domain); - if (r < 0) - return r; - if (r > 0) { - if (rr->nsec3.flags & 1) - /* This only makes sense if we have a wildcard delegation, which is - * very unlikely, see RFC 4592, Section 4.2, but we cannot rely on - * this not happening, so hence cannot simply conclude NXDOMAIN as - * we would wish */ - optout = true; - - a = a && (flags & DNS_ANSWER_AUTHENTICATED); - - no_wildcard = true; - } - } - - if (wildcard_rr && no_wildcard) - return -EBADMSG; - - if (!no_closer) { - *result = DNSSEC_NSEC_NO_RR; - return 0; - } - - if (wildcard_rr) { - /* A wildcard exists that matches our query. */ - if (optout) - /* This is not specified in any RFC to the best of my knowledge, but - * if the next closer enclosure is covered by an opt-out NSEC3 RR - * it means that we cannot prove that the source of synthesis is - * correct, as there may be a closer match. */ - *result = DNSSEC_NSEC_OPTOUT; - else if (bitmap_isset(wildcard_rr->nsec3.types, key->type)) - *result = DNSSEC_NSEC_FOUND; - else if (bitmap_isset(wildcard_rr->nsec3.types, DNS_TYPE_CNAME)) - *result = DNSSEC_NSEC_CNAME; - else - *result = DNSSEC_NSEC_NODATA; - } else { - if (optout) - /* The RFC only specifies that we have to care for optout for NODATA for - * DS records. However, children of an insecure opt-out delegation should - * also be considered opt-out, rather than verified NXDOMAIN. - * Note that we do not require a proof of wildcard non-existence if the - * next closer domain is covered by an opt-out, as that would not provide - * any additional information. */ - *result = DNSSEC_NSEC_OPTOUT; - else if (no_wildcard) - *result = DNSSEC_NSEC_NXDOMAIN; - else { - *result = DNSSEC_NSEC_NO_RR; - - return 0; - } - } - - if (authenticated) - *authenticated = a; - - if (ttl) - *ttl = enclosure_rr->ttl; - - return 0; -} - -static int dnssec_nsec_wildcard_equal(DnsResourceRecord *rr, const char *name) { - char label[DNS_LABEL_MAX]; - const char *n; - int r; - - assert(rr); - assert(rr->key->type == DNS_TYPE_NSEC); - - /* Checks whether the specified RR has a name beginning in "*.", and if the rest is a suffix of our name */ - - if (rr->n_skip_labels_source != 1) - return 0; - - n = dns_resource_key_name(rr->key); - r = dns_label_unescape(&n, label, sizeof(label)); - if (r <= 0) - return r; - if (r != 1 || label[0] != '*') - return 0; - - return dns_name_endswith(name, n); -} - -static int dnssec_nsec_in_path(DnsResourceRecord *rr, const char *name) { - const char *nn, *common_suffix; - int r; - - assert(rr); - assert(rr->key->type == DNS_TYPE_NSEC); - - /* Checks whether the specified nsec RR indicates that name is an empty non-terminal (ENT) - * - * A couple of examples: - * - * NSEC bar → waldo.foo.bar: indicates that foo.bar exists and is an ENT - * NSEC waldo.foo.bar → yyy.zzz.xoo.bar: indicates that xoo.bar and zzz.xoo.bar exist and are ENTs - * NSEC yyy.zzz.xoo.bar → bar: indicates pretty much nothing about ENTs - */ - - /* First, determine parent of next domain. */ - nn = rr->nsec.next_domain_name; - r = dns_name_parent(&nn); - if (r <= 0) - return r; - - /* If the name we just determined is not equal or child of the name we are interested in, then we can't say - * anything at all. */ - r = dns_name_endswith(nn, name); - if (r <= 0) - return r; - - /* If the name we are interested in is not a prefix of the common suffix of the NSEC RR's owner and next domain names, then we can't say anything either. */ - r = dns_name_common_suffix(dns_resource_key_name(rr->key), rr->nsec.next_domain_name, &common_suffix); - if (r < 0) - return r; - - return dns_name_endswith(name, common_suffix); -} - -static int dnssec_nsec_from_parent_zone(DnsResourceRecord *rr, const char *name) { - int r; - - assert(rr); - assert(rr->key->type == DNS_TYPE_NSEC); - - /* Checks whether this NSEC originates to the parent zone or the child zone. */ - - r = dns_name_parent(&name); - if (r <= 0) - return r; - - r = dns_name_equal(name, dns_resource_key_name(rr->key)); - if (r <= 0) - return r; - - /* DNAME, and NS without SOA is an indication for a delegation. */ - if (bitmap_isset(rr->nsec.types, DNS_TYPE_DNAME)) - return 1; - - if (bitmap_isset(rr->nsec.types, DNS_TYPE_NS) && !bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) - return 1; - - return 0; -} - -static int dnssec_nsec_covers(DnsResourceRecord *rr, const char *name) { - const char *common_suffix, *p; - int r; - - assert(rr); - assert(rr->key->type == DNS_TYPE_NSEC); - - /* Checks whether the "Next Closer" is witin the space covered by the specified RR. */ - - r = dns_name_common_suffix(dns_resource_key_name(rr->key), rr->nsec.next_domain_name, &common_suffix); - if (r < 0) - return r; - - for (;;) { - p = name; - r = dns_name_parent(&name); - if (r < 0) - return r; - if (r == 0) - return 0; - - r = dns_name_equal(name, common_suffix); - if (r < 0) - return r; - if (r > 0) - break; - } - - /* p is now the "Next Closer". */ - - return dns_name_between(dns_resource_key_name(rr->key), p, rr->nsec.next_domain_name); -} - -static int dnssec_nsec_covers_wildcard(DnsResourceRecord *rr, const char *name) { - const char *common_suffix, *wc; - int r; - - assert(rr); - assert(rr->key->type == DNS_TYPE_NSEC); - - /* Checks whether the "Wildcard at the Closest Encloser" is within the space covered by the specified - * RR. Specifically, checks whether 'name' has the common suffix of the NSEC RR's owner and next names as - * suffix, and whether the NSEC covers the name generated by that suffix prepended with an asterisk label. - * - * NSEC bar → waldo.foo.bar: indicates that *.bar and *.foo.bar do not exist - * NSEC waldo.foo.bar → yyy.zzz.xoo.bar: indicates that *.xoo.bar and *.zzz.xoo.bar do not exist (and more ...) - * NSEC yyy.zzz.xoo.bar → bar: indicates that a number of wildcards don#t exist either... - */ - - r = dns_name_common_suffix(dns_resource_key_name(rr->key), rr->nsec.next_domain_name, &common_suffix); - if (r < 0) - return r; - - /* If the common suffix is not shared by the name we are interested in, it has nothing to say for us. */ - r = dns_name_endswith(name, common_suffix); - if (r <= 0) - return r; - - wc = strjoina("*.", common_suffix); - return dns_name_between(dns_resource_key_name(rr->key), wc, rr->nsec.next_domain_name); -} - -int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { - bool have_nsec3 = false, covering_rr_authenticated = false, wildcard_rr_authenticated = false; - DnsResourceRecord *rr, *covering_rr = NULL, *wildcard_rr = NULL; - DnsAnswerFlags flags; - const char *name; - int r; - - assert(key); - assert(result); - - /* Look for any NSEC/NSEC3 RRs that say something about the specified key. */ - - name = dns_resource_key_name(key); - - DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { - - if (rr->key->class != key->class) - continue; - - have_nsec3 = have_nsec3 || (rr->key->type == DNS_TYPE_NSEC3); - - if (rr->key->type != DNS_TYPE_NSEC) - continue; - - /* The following checks only make sense for NSEC RRs that are not expanded from a wildcard */ - r = dns_resource_record_is_synthetic(rr); - if (r < 0) - return r; - if (r > 0) - continue; - - /* Check if this is a direct match. If so, we have encountered a NODATA case */ - r = dns_name_equal(dns_resource_key_name(rr->key), name); - if (r < 0) - return r; - if (r == 0) { - /* If it's not a direct match, maybe it's a wild card match? */ - r = dnssec_nsec_wildcard_equal(rr, name); - if (r < 0) - return r; - } - if (r > 0) { - if (key->type == DNS_TYPE_DS) { - /* If we look for a DS RR and the server sent us the NSEC RR of the child zone - * we have a problem. For DS RRs we want the NSEC RR from the parent */ - if (bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) - continue; - } else { - /* For all RR types, ensure that if NS is set SOA is set too, so that we know - * we got the child's NSEC. */ - if (bitmap_isset(rr->nsec.types, DNS_TYPE_NS) && - !bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) - continue; - } - - if (bitmap_isset(rr->nsec.types, key->type)) - *result = DNSSEC_NSEC_FOUND; - else if (bitmap_isset(rr->nsec.types, DNS_TYPE_CNAME)) - *result = DNSSEC_NSEC_CNAME; - else - *result = DNSSEC_NSEC_NODATA; - - if (authenticated) - *authenticated = flags & DNS_ANSWER_AUTHENTICATED; - if (ttl) - *ttl = rr->ttl; - - return 0; - } - - /* Check if the name we are looking for is an empty non-terminal within the owner or next name - * of the NSEC RR. */ - r = dnssec_nsec_in_path(rr, name); - if (r < 0) - return r; - if (r > 0) { - *result = DNSSEC_NSEC_NODATA; - - if (authenticated) - *authenticated = flags & DNS_ANSWER_AUTHENTICATED; - if (ttl) - *ttl = rr->ttl; - - return 0; - } - - /* The following two "covering" checks, are not useful if the NSEC is from the parent */ - r = dnssec_nsec_from_parent_zone(rr, name); - if (r < 0) - return r; - if (r > 0) - continue; - - /* Check if this NSEC RR proves the absence of an explicit RR under this name */ - r = dnssec_nsec_covers(rr, name); - if (r < 0) - return r; - if (r > 0 && (!covering_rr || !covering_rr_authenticated)) { - covering_rr = rr; - covering_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED; - } - - /* Check if this NSEC RR proves the absence of a wildcard RR under this name */ - r = dnssec_nsec_covers_wildcard(rr, name); - if (r < 0) - return r; - if (r > 0 && (!wildcard_rr || !wildcard_rr_authenticated)) { - wildcard_rr = rr; - wildcard_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED; - } - } - - if (covering_rr && wildcard_rr) { - /* If we could prove that neither the name itself, nor the wildcard at the closest encloser exists, we - * proved the NXDOMAIN case. */ - *result = DNSSEC_NSEC_NXDOMAIN; - - if (authenticated) - *authenticated = covering_rr_authenticated && wildcard_rr_authenticated; - if (ttl) - *ttl = MIN(covering_rr->ttl, wildcard_rr->ttl); - - return 0; - } - - /* OK, this was not sufficient. Let's see if NSEC3 can help. */ - if (have_nsec3) - return dnssec_test_nsec3(answer, key, result, authenticated, ttl); - - /* No approproate NSEC RR found, report this. */ - *result = DNSSEC_NSEC_NO_RR; - return 0; -} - -static int dnssec_nsec_test_enclosed(DnsAnswer *answer, uint16_t type, const char *name, const char *zone, bool *authenticated) { - DnsResourceRecord *rr; - DnsAnswerFlags flags; - int r; - - assert(name); - assert(zone); - - /* Checks whether there's an NSEC/NSEC3 that proves that the specified 'name' is non-existing in the specified - * 'zone'. The 'zone' must be a suffix of the 'name'. */ - - DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { - bool found = false; - - if (rr->key->type != type && type != DNS_TYPE_ANY) - continue; - - switch (rr->key->type) { - - case DNS_TYPE_NSEC: - - /* We only care for NSEC RRs from the indicated zone */ - r = dns_resource_record_is_signer(rr, zone); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dns_name_between(dns_resource_key_name(rr->key), name, rr->nsec.next_domain_name); - if (r < 0) - return r; - - found = r > 0; - break; - - case DNS_TYPE_NSEC3: { - _cleanup_free_ char *hashed_domain = NULL, *next_hashed_domain = NULL; - - /* We only care for NSEC3 RRs from the indicated zone */ - r = dns_resource_record_is_signer(rr, zone); - if (r < 0) - return r; - if (r == 0) - continue; - - r = nsec3_is_good(rr, NULL); - if (r < 0) - return r; - if (r == 0) - break; - - /* Format the domain we are testing with the NSEC3 RR's hash function */ - r = nsec3_hashed_domain_make( - rr, - name, - zone, - &hashed_domain); - if (r < 0) - return r; - if ((size_t) r != rr->nsec3.next_hashed_name_size) - break; - - /* Format the NSEC3's next hashed name as proper domain name */ - r = nsec3_hashed_domain_format( - rr->nsec3.next_hashed_name, - rr->nsec3.next_hashed_name_size, - zone, - &next_hashed_domain); - if (r < 0) - return r; - - r = dns_name_between(dns_resource_key_name(rr->key), hashed_domain, next_hashed_domain); - if (r < 0) - return r; - - found = r > 0; - break; - } - - default: - continue; - } - - if (found) { - if (authenticated) - *authenticated = flags & DNS_ANSWER_AUTHENTICATED; - return 1; - } - } - - return 0; -} - -static int dnssec_test_positive_wildcard_nsec3( - DnsAnswer *answer, - const char *name, - const char *source, - const char *zone, - bool *authenticated) { - - const char *next_closer = NULL; - int r; - - /* Run a positive NSEC3 wildcard proof. Specifically: - * - * A proof that the "next closer" of the generating wildcard does not exist. - * - * Note a key difference between the NSEC3 and NSEC versions of the proof. NSEC RRs don't have to exist for - * empty non-transients. NSEC3 RRs however have to. This means it's sufficient to check if the next closer name - * exists for the NSEC3 RR and we are done. - * - * To prove that a.b.c.d.e.f is rightfully synthesized from a wildcard *.d.e.f all we have to check is that - * c.d.e.f does not exist. */ - - for (;;) { - next_closer = name; - r = dns_name_parent(&name); - if (r < 0) - return r; - if (r == 0) - return 0; - - r = dns_name_equal(name, source); - if (r < 0) - return r; - if (r > 0) - break; - } - - return dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC3, next_closer, zone, authenticated); -} - -static int dnssec_test_positive_wildcard_nsec( - DnsAnswer *answer, - const char *name, - const char *source, - const char *zone, - bool *_authenticated) { - - bool authenticated = true; - int r; - - /* Run a positive NSEC wildcard proof. Specifically: - * - * A proof that there's neither a wildcard name nor a non-wildcard name that is a suffix of the name "name" and - * a prefix of the synthesizing source "source" in the zone "zone". - * - * See RFC 5155, Section 8.8 and RFC 4035, Section 5.3.4 - * - * Note that if we want to prove that a.b.c.d.e.f is rightfully synthesized from a wildcard *.d.e.f, then we - * have to prove that none of the following exist: - * - * 1) a.b.c.d.e.f - * 2) *.b.c.d.e.f - * 3) b.c.d.e.f - * 4) *.c.d.e.f - * 5) c.d.e.f - * - */ - - for (;;) { - _cleanup_free_ char *wc = NULL; - bool a = false; - - /* Check if there's an NSEC or NSEC3 RR that proves that the mame we determined is really non-existing, - * i.e between the owner name and the next name of an NSEC RR. */ - r = dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC, name, zone, &a); - if (r <= 0) - return r; - - authenticated = authenticated && a; - - /* Strip one label off */ - r = dns_name_parent(&name); - if (r <= 0) - return r; - - /* Did we reach the source of synthesis? */ - r = dns_name_equal(name, source); - if (r < 0) - return r; - if (r > 0) { - /* Successful exit */ - *_authenticated = authenticated; - return 1; - } - - /* Safety check, that the source of synthesis is still our suffix */ - r = dns_name_endswith(name, source); - if (r < 0) - return r; - if (r == 0) - return -EBADMSG; - - /* Replace the label we stripped off with an asterisk */ - wc = strappend("*.", name); - if (!wc) - return -ENOMEM; - - /* And check if the proof holds for the asterisk name, too */ - r = dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC, wc, zone, &a); - if (r <= 0) - return r; - - authenticated = authenticated && a; - /* In the next iteration we'll check the non-asterisk-prefixed version */ - } -} - -int dnssec_test_positive_wildcard( - DnsAnswer *answer, - const char *name, - const char *source, - const char *zone, - bool *authenticated) { - - int r; - - assert(name); - assert(source); - assert(zone); - assert(authenticated); - - r = dns_answer_contains_zone_nsec3(answer, zone); - if (r < 0) - return r; - if (r > 0) - return dnssec_test_positive_wildcard_nsec3(answer, name, source, zone, authenticated); - else - return dnssec_test_positive_wildcard_nsec(answer, name, source, zone, authenticated); -} - -#else - -int dnssec_verify_rrset( - DnsAnswer *a, - const DnsResourceKey *key, - DnsResourceRecord *rrsig, - DnsResourceRecord *dnskey, - usec_t realtime, - DnssecResult *result) { - - return -EOPNOTSUPP; -} - -int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) { - - return -EOPNOTSUPP; -} - -int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) { - - return -EOPNOTSUPP; -} - -int dnssec_verify_rrset_search( - DnsAnswer *a, - const DnsResourceKey *key, - DnsAnswer *validated_dnskeys, - usec_t realtime, - DnssecResult *result, - DnsResourceRecord **ret_rrsig) { - - return -EOPNOTSUPP; -} - -int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) { - - return -EOPNOTSUPP; -} - -int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) { - - return -EOPNOTSUPP; -} - -int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) { - - return -EOPNOTSUPP; -} - -int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { - - return -EOPNOTSUPP; -} - -int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { - - return -EOPNOTSUPP; -} - -int dnssec_test_positive_wildcard( - DnsAnswer *answer, - const char *name, - const char *source, - const char *zone, - bool *authenticated) { - - return -EOPNOTSUPP; -} - -#endif - -static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = { - [DNSSEC_VALIDATED] = "validated", - [DNSSEC_VALIDATED_WILDCARD] = "validated-wildcard", - [DNSSEC_INVALID] = "invalid", - [DNSSEC_SIGNATURE_EXPIRED] = "signature-expired", - [DNSSEC_UNSUPPORTED_ALGORITHM] = "unsupported-algorithm", - [DNSSEC_NO_SIGNATURE] = "no-signature", - [DNSSEC_MISSING_KEY] = "missing-key", - [DNSSEC_UNSIGNED] = "unsigned", - [DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary", - [DNSSEC_NSEC_MISMATCH] = "nsec-mismatch", - [DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server", -}; -DEFINE_STRING_TABLE_LOOKUP(dnssec_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 deleted file mode 100644 index 77bd4d71bf..0000000000 --- a/src/resolve/resolved-dns-dnssec.h +++ /dev/null @@ -1,102 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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/>. -***/ - -typedef enum DnssecResult DnssecResult; -typedef enum DnssecVerdict DnssecVerdict; - -#include "dns-domain.h" -#include "resolved-dns-answer.h" -#include "resolved-dns-rr.h" - -enum DnssecResult { - /* These five are returned by dnssec_verify_rrset() */ - DNSSEC_VALIDATED, - DNSSEC_VALIDATED_WILDCARD, /* Validated via a wildcard RRSIG, further NSEC/NSEC3 checks necessary */ - DNSSEC_INVALID, - DNSSEC_SIGNATURE_EXPIRED, - DNSSEC_UNSUPPORTED_ALGORITHM, - - /* These two are added by dnssec_verify_rrset_search() */ - DNSSEC_NO_SIGNATURE, - DNSSEC_MISSING_KEY, - - /* These two are added by the DnsTransaction logic */ - DNSSEC_UNSIGNED, - DNSSEC_FAILED_AUXILIARY, - DNSSEC_NSEC_MISMATCH, - DNSSEC_INCOMPATIBLE_SERVER, - - _DNSSEC_RESULT_MAX, - _DNSSEC_RESULT_INVALID = -1 -}; - -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 */ -#define DNSSEC_HASH_SIZE_MAX (MAX(20, 32)) - -int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok); -int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig); - -int dnssec_verify_rrset(DnsAnswer *answer, const DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime, DnssecResult *result); -int dnssec_verify_rrset_search(DnsAnswer *answer, const DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime, DnssecResult *result, DnsResourceRecord **rrsig); - -int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke); -int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds); - -int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key); - -uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke); - -int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max); - -int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret); - -typedef enum DnssecNsecResult { - DNSSEC_NSEC_NO_RR, /* No suitable NSEC/NSEC3 RR found */ - DNSSEC_NSEC_CNAME, /* Didn't find what was asked for, but did find CNAME */ - DNSSEC_NSEC_UNSUPPORTED_ALGORITHM, - DNSSEC_NSEC_NXDOMAIN, - DNSSEC_NSEC_NODATA, - DNSSEC_NSEC_FOUND, - DNSSEC_NSEC_OPTOUT, -} DnssecNsecResult; - -int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl); - - -int dnssec_test_positive_wildcard(DnsAnswer *a, const char *name, const char *source, const char *zone, bool *authenticated); - -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 deleted file mode 100644 index 337a8c473f..0000000000 --- a/src/resolve/resolved-dns-packet.c +++ /dev/null @@ -1,2301 +0,0 @@ -/*** - 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 "dns-domain.h" -#include "resolved-dns-packet.h" -#include "string-table.h" -#include "strv.h" -#include "unaligned.h" -#include "utf8.h" -#include "util.h" - -#define EDNS0_OPT_DO (1<<15) - -typedef struct DnsPacketRewinder { - DnsPacket *packet; - size_t saved_rindex; -} DnsPacketRewinder; - -static void rewind_dns_packet(DnsPacketRewinder *rewinder) { - if (rewinder->packet) - dns_packet_rewind(rewinder->packet, rewinder->saved_rindex); -} - -#define INIT_REWINDER(rewinder, p) do { rewinder.packet = p; rewinder.saved_rindex = p->rindex; } while (0) -#define CANCEL_REWINDER(rewinder) do { rewinder.packet = NULL; } while (0) - -int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { - DnsPacket *p; - size_t a; - - assert(ret); - - if (mtu <= UDP_PACKET_HEADER_SIZE) - a = DNS_PACKET_SIZE_START; - else - a = mtu - UDP_PACKET_HEADER_SIZE; - - if (a < DNS_PACKET_HEADER_SIZE) - a = DNS_PACKET_HEADER_SIZE; - - /* round up to next page size */ - a = PAGE_ALIGN(ALIGN(sizeof(DnsPacket)) + a) - ALIGN(sizeof(DnsPacket)); - - /* make sure we never allocate more than useful */ - if (a > DNS_PACKET_SIZE_MAX) - a = DNS_PACKET_SIZE_MAX; - - p = malloc0(ALIGN(sizeof(DnsPacket)) + a); - if (!p) - return -ENOMEM; - - p->size = p->rindex = DNS_PACKET_HEADER_SIZE; - p->allocated = a; - p->protocol = protocol; - p->opt_start = p->opt_size = (size_t) -1; - p->n_ref = 1; - - *ret = p; - - return 0; -} - -void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated) { - - DnsPacketHeader *h; - - assert(p); - - h = DNS_PACKET_HEADER(p); - - switch(p->protocol) { - case DNS_PROTOCOL_LLMNR: - assert(!truncated); - - h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, - 0 /* opcode */, - 0 /* c */, - 0 /* tc */, - 0 /* t */, - 0 /* ra */, - 0 /* ad */, - 0 /* cd */, - 0 /* rcode */)); - break; - - case DNS_PROTOCOL_MDNS: - h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, - 0 /* opcode */, - 0 /* aa */, - truncated /* tc */, - 0 /* rd (ask for recursion) */, - 0 /* ra */, - 0 /* ad */, - 0 /* cd */, - 0 /* rcode */)); - break; - - default: - assert(!truncated); - - h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, - 0 /* opcode */, - 0 /* aa */, - 0 /* tc */, - 1 /* rd (ask for recursion) */, - 0 /* ra */, - 0 /* ad */, - dnssec_checking_disabled /* cd */, - 0 /* rcode */)); - } -} - -int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled) { - DnsPacket *p; - int r; - - assert(ret); - - r = dns_packet_new(&p, protocol, mtu); - if (r < 0) - return r; - - /* Always set the TC bit to 0 initially. - * If there are multiple packets later, we'll update the bit shortly before sending. - */ - dns_packet_set_flags(p, dnssec_checking_disabled, false); - - *ret = p; - return 0; -} - -DnsPacket *dns_packet_ref(DnsPacket *p) { - - if (!p) - return NULL; - - assert(!p->on_stack); - - assert(p->n_ref > 0); - p->n_ref++; - return p; -} - -static void dns_packet_free(DnsPacket *p) { - char *s; - - assert(p); - - dns_question_unref(p->question); - dns_answer_unref(p->answer); - dns_resource_record_unref(p->opt); - - while ((s = hashmap_steal_first_key(p->names))) - free(s); - hashmap_free(p->names); - - free(p->_data); - - if (!p->on_stack) - free(p); -} - -DnsPacket *dns_packet_unref(DnsPacket *p) { - if (!p) - return NULL; - - assert(p->n_ref > 0); - - dns_packet_unref(p->more); - - if (p->n_ref == 1) - dns_packet_free(p); - else - p->n_ref--; - - return NULL; -} - -int dns_packet_validate(DnsPacket *p) { - assert(p); - - if (p->size < DNS_PACKET_HEADER_SIZE) - return -EBADMSG; - - if (p->size > DNS_PACKET_SIZE_MAX) - return -EBADMSG; - - return 1; -} - -int dns_packet_validate_reply(DnsPacket *p) { - int r; - - assert(p); - - r = dns_packet_validate(p); - if (r < 0) - return r; - - if (DNS_PACKET_QR(p) != 1) - return 0; - - if (DNS_PACKET_OPCODE(p) != 0) - return -EBADMSG; - - switch (p->protocol) { - - case DNS_PROTOCOL_LLMNR: - /* RFC 4795, Section 2.1.1. says to discard all replies with QDCOUNT != 1 */ - if (DNS_PACKET_QDCOUNT(p) != 1) - return -EBADMSG; - - break; - - case DNS_PROTOCOL_MDNS: - /* RFC 6762, Section 18 */ - if (DNS_PACKET_RCODE(p) != 0) - return -EBADMSG; - - break; - - default: - break; - } - - return 1; -} - -int dns_packet_validate_query(DnsPacket *p) { - int r; - - assert(p); - - r = dns_packet_validate(p); - if (r < 0) - return r; - - if (DNS_PACKET_QR(p) != 0) - return 0; - - if (DNS_PACKET_OPCODE(p) != 0) - return -EBADMSG; - - if (DNS_PACKET_TC(p)) - return -EBADMSG; - - switch (p->protocol) { - - case DNS_PROTOCOL_LLMNR: - case DNS_PROTOCOL_DNS: - /* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */ - if (DNS_PACKET_QDCOUNT(p) != 1) - return -EBADMSG; - - /* RFC 4795, Section 2.1.1. says to discard all queries with ANCOUNT != 0 */ - if (DNS_PACKET_ANCOUNT(p) > 0) - return -EBADMSG; - - /* RFC 4795, Section 2.1.1. says to discard all queries with NSCOUNT != 0 */ - if (DNS_PACKET_NSCOUNT(p) > 0) - return -EBADMSG; - - break; - - case DNS_PROTOCOL_MDNS: - /* RFC 6762, Section 18 */ - if (DNS_PACKET_AA(p) != 0 || - DNS_PACKET_RD(p) != 0 || - DNS_PACKET_RA(p) != 0 || - DNS_PACKET_AD(p) != 0 || - DNS_PACKET_CD(p) != 0 || - DNS_PACKET_RCODE(p) != 0) - return -EBADMSG; - - break; - - default: - break; - } - - return 1; -} - -static int dns_packet_extend(DnsPacket *p, size_t add, void **ret, size_t *start) { - assert(p); - - if (p->size + add > p->allocated) { - size_t a; - - a = PAGE_ALIGN((p->size + add) * 2); - if (a > DNS_PACKET_SIZE_MAX) - a = DNS_PACKET_SIZE_MAX; - - if (p->size + add > a) - return -EMSGSIZE; - - if (p->_data) { - void *d; - - d = realloc(p->_data, a); - if (!d) - return -ENOMEM; - - p->_data = d; - } else { - p->_data = malloc(a); - if (!p->_data) - return -ENOMEM; - - memcpy(p->_data, (uint8_t*) p + ALIGN(sizeof(DnsPacket)), p->size); - memzero((uint8_t*) p->_data + p->size, a - p->size); - } - - p->allocated = a; - } - - if (start) - *start = p->size; - - if (ret) - *ret = (uint8_t*) DNS_PACKET_DATA(p) + p->size; - - p->size += add; - return 0; -} - -void dns_packet_truncate(DnsPacket *p, size_t sz) { - Iterator i; - char *s; - void *n; - - assert(p); - - if (p->size <= sz) - return; - - HASHMAP_FOREACH_KEY(n, s, p->names, i) { - - if (PTR_TO_SIZE(n) < sz) - continue; - - hashmap_remove(p->names, s); - free(s); - } - - p->size = sz; -} - -int dns_packet_append_blob(DnsPacket *p, const void *d, size_t l, size_t *start) { - void *q; - int r; - - assert(p); - - r = dns_packet_extend(p, l, &q, start); - if (r < 0) - return r; - - memcpy(q, d, l); - return 0; -} - -int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start) { - void *d; - int r; - - assert(p); - - r = dns_packet_extend(p, sizeof(uint8_t), &d, start); - if (r < 0) - return r; - - ((uint8_t*) d)[0] = v; - - return 0; -} - -int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start) { - void *d; - int r; - - assert(p); - - r = dns_packet_extend(p, sizeof(uint16_t), &d, start); - if (r < 0) - return r; - - unaligned_write_be16(d, v); - - return 0; -} - -int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start) { - void *d; - int r; - - assert(p); - - r = dns_packet_extend(p, sizeof(uint32_t), &d, start); - if (r < 0) - return r; - - unaligned_write_be32(d, v); - - return 0; -} - -int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start) { - assert(p); - assert(s); - - return dns_packet_append_raw_string(p, s, strlen(s), start); -} - -int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start) { - void *d; - int r; - - assert(p); - assert(s || size == 0); - - if (size > 255) - return -E2BIG; - - r = dns_packet_extend(p, 1 + size, &d, start); - if (r < 0) - return r; - - ((uint8_t*) d)[0] = (uint8_t) size; - - memcpy_safe(((uint8_t*) d) + 1, s, size); - - return 0; -} - -int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, bool canonical_candidate, size_t *start) { - uint8_t *w; - int r; - - /* Append a label to a packet. Optionally, does this in DNSSEC - * canonical form, if this label is marked as a candidate for - * it, and the canonical form logic is enabled for the - * packet */ - - assert(p); - assert(d); - - if (l > DNS_LABEL_MAX) - return -E2BIG; - - r = dns_packet_extend(p, 1 + l, (void**) &w, start); - if (r < 0) - return r; - - *(w++) = (uint8_t) l; - - if (p->canonical_form && canonical_candidate) { - size_t i; - - /* Generate in canonical form, as defined by DNSSEC - * RFC 4034, Section 6.2, i.e. all lower-case. */ - - for (i = 0; i < l; i++) - w[i] = (uint8_t) ascii_tolower(d[i]); - } else - /* Otherwise, just copy the string unaltered. This is - * essential for DNS-SD, where the casing of labels - * matters and needs to be retained. */ - memcpy(w, d, l); - - return 0; -} - -int dns_packet_append_name( - DnsPacket *p, - const char *name, - bool allow_compression, - bool canonical_candidate, - size_t *start) { - - size_t saved_size; - int r; - - assert(p); - assert(name); - - if (p->refuse_compression) - allow_compression = false; - - saved_size = p->size; - - while (!dns_name_is_root(name)) { - const char *z = name; - char label[DNS_LABEL_MAX]; - size_t n = 0; - - if (allow_compression) - n = PTR_TO_SIZE(hashmap_get(p->names, name)); - if (n > 0) { - assert(n < p->size); - - if (n < 0x4000) { - r = dns_packet_append_uint16(p, 0xC000 | n, NULL); - if (r < 0) - goto fail; - - goto done; - } - } - - r = dns_label_unescape(&name, label, sizeof(label)); - if (r < 0) - goto fail; - - r = dns_packet_append_label(p, label, r, canonical_candidate, &n); - if (r < 0) - goto fail; - - if (allow_compression) { - _cleanup_free_ char *s = NULL; - - s = strdup(z); - if (!s) { - r = -ENOMEM; - goto fail; - } - - r = hashmap_ensure_allocated(&p->names, &dns_name_hash_ops); - if (r < 0) - goto fail; - - r = hashmap_put(p->names, s, SIZE_TO_PTR(n)); - if (r < 0) - goto fail; - - s = NULL; - } - } - - r = dns_packet_append_uint8(p, 0, NULL); - if (r < 0) - return r; - -done: - if (start) - *start = saved_size; - - return 0; - -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start) { - size_t saved_size; - int r; - - assert(p); - assert(k); - - saved_size = p->size; - - r = dns_packet_append_name(p, dns_resource_key_name(k), true, true, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, k->type, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, k->class, NULL); - if (r < 0) - goto fail; - - if (start) - *start = saved_size; - - return 0; - -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t length, const uint8_t *types, size_t *start) { - size_t saved_size; - int r; - - assert(p); - assert(types); - assert(length > 0); - - saved_size = p->size; - - r = dns_packet_append_uint8(p, window, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, length, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, types, length, NULL); - if (r < 0) - goto fail; - - if (start) - *start = saved_size; - - return 0; -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) { - Iterator i; - uint8_t window = 0; - uint8_t entry = 0; - uint8_t bitmaps[32] = {}; - unsigned n; - size_t saved_size; - int r; - - assert(p); - - saved_size = p->size; - - BITMAP_FOREACH(n, types, i) { - assert(n <= 0xffff); - - if ((n >> 8) != window && bitmaps[entry / 8] != 0) { - r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); - if (r < 0) - goto fail; - - zero(bitmaps); - } - - window = n >> 8; - entry = n & 255; - - bitmaps[entry / 8] |= 1 << (7 - (entry % 8)); - } - - if (bitmaps[entry / 8] != 0) { - r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); - if (r < 0) - goto fail; - } - - if (start) - *start = saved_size; - - return 0; -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -/* Append the OPT pseudo-RR described in RFC6891 */ -int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, int rcode, size_t *start) { - size_t saved_size; - int r; - - assert(p); - /* we must never advertise supported packet size smaller than the legacy max */ - assert(max_udp_size >= DNS_PACKET_UNICAST_SIZE_MAX); - assert(rcode >= 0); - assert(rcode <= _DNS_RCODE_MAX); - - if (p->opt_start != (size_t) -1) - return -EBUSY; - - assert(p->opt_size == (size_t) -1); - - saved_size = p->size; - - /* empty name */ - r = dns_packet_append_uint8(p, 0, NULL); - if (r < 0) - return r; - - /* type */ - r = dns_packet_append_uint16(p, DNS_TYPE_OPT, NULL); - if (r < 0) - goto fail; - - /* class: maximum udp packet that can be received */ - r = dns_packet_append_uint16(p, max_udp_size, NULL); - if (r < 0) - goto fail; - - /* extended RCODE and VERSION */ - r = dns_packet_append_uint16(p, ((uint16_t) rcode & 0x0FF0) << 4, NULL); - if (r < 0) - goto fail; - - /* flags: DNSSEC OK (DO), see RFC3225 */ - r = dns_packet_append_uint16(p, edns0_do ? EDNS0_OPT_DO : 0, NULL); - if (r < 0) - goto fail; - - /* RDLENGTH */ - if (edns0_do && !DNS_PACKET_QR(p)) { - /* If DO is on and this is not a reply, also append RFC6975 Algorithm data */ - - static const uint8_t rfc6975[] = { - - 0, 5, /* OPTION_CODE: DAU */ - 0, 6, /* LIST_LENGTH */ - DNSSEC_ALGORITHM_RSASHA1, - DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, - DNSSEC_ALGORITHM_RSASHA256, - DNSSEC_ALGORITHM_RSASHA512, - DNSSEC_ALGORITHM_ECDSAP256SHA256, - DNSSEC_ALGORITHM_ECDSAP384SHA384, - - 0, 6, /* OPTION_CODE: DHU */ - 0, 3, /* LIST_LENGTH */ - DNSSEC_DIGEST_SHA1, - DNSSEC_DIGEST_SHA256, - DNSSEC_DIGEST_SHA384, - - 0, 7, /* OPTION_CODE: N3U */ - 0, 1, /* LIST_LENGTH */ - NSEC3_ALGORITHM_SHA1, - }; - - r = dns_packet_append_uint16(p, sizeof(rfc6975), NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rfc6975, sizeof(rfc6975), NULL); - } else - r = dns_packet_append_uint16(p, 0, NULL); - if (r < 0) - goto fail; - - DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) + 1); - - p->opt_start = saved_size; - p->opt_size = p->size - saved_size; - - if (start) - *start = saved_size; - - return 0; - -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -int dns_packet_truncate_opt(DnsPacket *p) { - assert(p); - - if (p->opt_start == (size_t) -1) { - assert(p->opt_size == (size_t) -1); - return 0; - } - - assert(p->opt_size != (size_t) -1); - assert(DNS_PACKET_ARCOUNT(p) > 0); - - if (p->opt_start + p->opt_size != p->size) - return -EBUSY; - - dns_packet_truncate(p, p->opt_start); - DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) - 1); - p->opt_start = p->opt_size = (size_t) -1; - - return 1; -} - -int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start) { - - size_t saved_size, rdlength_offset, end, rdlength, rds; - int r; - - assert(p); - assert(rr); - - saved_size = p->size; - - r = dns_packet_append_key(p, rr->key, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->ttl, NULL); - if (r < 0) - goto fail; - - /* Initially we write 0 here */ - r = dns_packet_append_uint16(p, 0, &rdlength_offset); - if (r < 0) - goto fail; - - rds = p->size - saved_size; - - switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { - - case DNS_TYPE_SRV: - r = dns_packet_append_uint16(p, rr->srv.priority, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, rr->srv.weight, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, rr->srv.port, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_name(p, rr->srv.name, true, false, NULL); - break; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - r = dns_packet_append_name(p, rr->ptr.name, true, false, NULL); - break; - - case DNS_TYPE_HINFO: - r = dns_packet_append_string(p, rr->hinfo.cpu, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_string(p, rr->hinfo.os, NULL); - break; - - case DNS_TYPE_SPF: /* exactly the same as TXT */ - case DNS_TYPE_TXT: - - if (!rr->txt.items) { - /* RFC 6763, section 6.1 suggests to generate - * single empty string for an empty array. */ - - r = dns_packet_append_raw_string(p, NULL, 0, NULL); - if (r < 0) - goto fail; - } else { - DnsTxtItem *i; - - LIST_FOREACH(items, i, rr->txt.items) { - r = dns_packet_append_raw_string(p, i->data, i->length, NULL); - if (r < 0) - goto fail; - } - } - - r = 0; - break; - - case DNS_TYPE_A: - r = dns_packet_append_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL); - break; - - case DNS_TYPE_AAAA: - r = dns_packet_append_blob(p, &rr->aaaa.in6_addr, sizeof(struct in6_addr), NULL); - break; - - case DNS_TYPE_SOA: - r = dns_packet_append_name(p, rr->soa.mname, true, false, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_name(p, rr->soa.rname, true, false, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->soa.serial, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->soa.refresh, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->soa.retry, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->soa.expire, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->soa.minimum, NULL); - break; - - case DNS_TYPE_MX: - r = dns_packet_append_uint16(p, rr->mx.priority, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_name(p, rr->mx.exchange, true, false, NULL); - break; - - case DNS_TYPE_LOC: - r = dns_packet_append_uint8(p, rr->loc.version, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->loc.size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->loc.horiz_pre, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->loc.vert_pre, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->loc.latitude, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->loc.longitude, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->loc.altitude, NULL); - break; - - case DNS_TYPE_DS: - r = dns_packet_append_uint16(p, rr->ds.key_tag, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->ds.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->ds.digest_type, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->ds.digest, rr->ds.digest_size, NULL); - break; - - case DNS_TYPE_SSHFP: - r = dns_packet_append_uint8(p, rr->sshfp.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->sshfp.fptype, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, NULL); - break; - - case DNS_TYPE_DNSKEY: - r = dns_packet_append_uint16(p, rr->dnskey.flags, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->dnskey.protocol, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->dnskey.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->dnskey.key, rr->dnskey.key_size, NULL); - break; - - case DNS_TYPE_RRSIG: - r = dns_packet_append_uint16(p, rr->rrsig.type_covered, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->rrsig.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->rrsig.labels, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->rrsig.original_ttl, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->rrsig.expiration, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->rrsig.inception, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, rr->rrsig.key_tag, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_name(p, rr->rrsig.signer, false, true, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->rrsig.signature, rr->rrsig.signature_size, NULL); - break; - - case DNS_TYPE_NSEC: - r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, false, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_types(p, rr->nsec.types, NULL); - if (r < 0) - goto fail; - - break; - - case DNS_TYPE_NSEC3: - r = dns_packet_append_uint8(p, rr->nsec3.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->nsec3.flags, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, rr->nsec3.iterations, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->nsec3.salt_size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->nsec3.salt, rr->nsec3.salt_size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->nsec3.next_hashed_name_size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_types(p, rr->nsec3.types, NULL); - if (r < 0) - goto fail; - - break; - - case DNS_TYPE_TLSA: - r = dns_packet_append_uint8(p, rr->tlsa.cert_usage, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->tlsa.selector, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->tlsa.matching_type, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->tlsa.data, rr->tlsa.data_size, NULL); - break; - - case DNS_TYPE_CAA: - r = dns_packet_append_uint8(p, rr->caa.flags, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_string(p, rr->caa.tag, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->caa.value, rr->caa.value_size, NULL); - break; - - case DNS_TYPE_OPT: - case DNS_TYPE_OPENPGPKEY: - case _DNS_TYPE_INVALID: /* unparseable */ - default: - - r = dns_packet_append_blob(p, rr->generic.data, rr->generic.data_size, NULL); - break; - } - if (r < 0) - goto fail; - - /* Let's calculate the actual data size and update the field */ - rdlength = p->size - rdlength_offset - sizeof(uint16_t); - if (rdlength > 0xFFFF) { - r = -ENOSPC; - goto fail; - } - - end = p->size; - p->size = rdlength_offset; - r = dns_packet_append_uint16(p, rdlength, NULL); - if (r < 0) - goto fail; - p->size = end; - - if (start) - *start = saved_size; - - if (rdata_start) - *rdata_start = rds; - - return 0; - -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -int dns_packet_append_question(DnsPacket *p, DnsQuestion *q) { - DnsResourceKey *key; - int r; - - assert(p); - - DNS_QUESTION_FOREACH(key, q) { - r = dns_packet_append_key(p, key, NULL); - if (r < 0) - return r; - } - - return 0; -} - -int dns_packet_append_answer(DnsPacket *p, DnsAnswer *a) { - DnsResourceRecord *rr; - int r; - - assert(p); - - DNS_ANSWER_FOREACH(rr, a) { - r = dns_packet_append_rr(p, rr, NULL, NULL); - if (r < 0) - return r; - } - - return 0; -} - -int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start) { - assert(p); - - if (p->rindex + sz > p->size) - return -EMSGSIZE; - - if (ret) - *ret = (uint8_t*) DNS_PACKET_DATA(p) + p->rindex; - - if (start) - *start = p->rindex; - - p->rindex += sz; - return 0; -} - -void dns_packet_rewind(DnsPacket *p, size_t idx) { - assert(p); - assert(idx <= p->size); - assert(idx >= DNS_PACKET_HEADER_SIZE); - - p->rindex = idx; -} - -int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start) { - const void *q; - int r; - - assert(p); - assert(d); - - r = dns_packet_read(p, sz, &q, start); - if (r < 0) - return r; - - memcpy(d, q, sz); - return 0; -} - -static int dns_packet_read_memdup( - DnsPacket *p, size_t size, - void **ret, size_t *ret_size, - size_t *ret_start) { - - const void *src; - size_t start; - int r; - - assert(p); - assert(ret); - - r = dns_packet_read(p, size, &src, &start); - if (r < 0) - return r; - - if (size <= 0) - *ret = NULL; - else { - void *copy; - - copy = memdup(src, size); - if (!copy) - return -ENOMEM; - - *ret = copy; - } - - if (ret_size) - *ret_size = size; - if (ret_start) - *ret_start = start; - - return 0; -} - -int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start) { - const void *d; - int r; - - assert(p); - - r = dns_packet_read(p, sizeof(uint8_t), &d, start); - if (r < 0) - return r; - - *ret = ((uint8_t*) d)[0]; - return 0; -} - -int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start) { - const void *d; - int r; - - assert(p); - - r = dns_packet_read(p, sizeof(uint16_t), &d, start); - if (r < 0) - return r; - - *ret = unaligned_read_be16(d); - - return 0; -} - -int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start) { - const void *d; - int r; - - assert(p); - - r = dns_packet_read(p, sizeof(uint32_t), &d, start); - if (r < 0) - return r; - - *ret = unaligned_read_be32(d); - - return 0; -} - -int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start) { - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; - const void *d; - char *t; - uint8_t c; - int r; - - assert(p); - INIT_REWINDER(rewinder, p); - - r = dns_packet_read_uint8(p, &c, NULL); - if (r < 0) - return r; - - r = dns_packet_read(p, c, &d, NULL); - if (r < 0) - return r; - - if (memchr(d, 0, c)) - return -EBADMSG; - - t = strndup(d, c); - if (!t) - return -ENOMEM; - - if (!utf8_is_valid(t)) { - free(t); - return -EBADMSG; - } - - *ret = t; - - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); - - return 0; -} - -int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start) { - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; - uint8_t c; - int r; - - assert(p); - INIT_REWINDER(rewinder, p); - - r = dns_packet_read_uint8(p, &c, NULL); - if (r < 0) - return r; - - r = dns_packet_read(p, c, ret, NULL); - if (r < 0) - return r; - - if (size) - *size = c; - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); - - return 0; -} - -int dns_packet_read_name( - DnsPacket *p, - char **_ret, - bool allow_compression, - size_t *start) { - - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; - size_t after_rindex = 0, jump_barrier; - _cleanup_free_ char *ret = NULL; - size_t n = 0, allocated = 0; - bool first = true; - int r; - - assert(p); - assert(_ret); - INIT_REWINDER(rewinder, p); - jump_barrier = p->rindex; - - if (p->refuse_compression) - allow_compression = false; - - for (;;) { - uint8_t c, d; - - r = dns_packet_read_uint8(p, &c, NULL); - if (r < 0) - return r; - - if (c == 0) - /* End of name */ - break; - else if (c <= 63) { - const char *label; - - /* Literal label */ - r = dns_packet_read(p, c, (const void**) &label, NULL); - if (r < 0) - return r; - - if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) - return -ENOMEM; - - if (first) - first = false; - else - ret[n++] = '.'; - - r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX); - if (r < 0) - return r; - - n += r; - continue; - } else if (allow_compression && (c & 0xc0) == 0xc0) { - uint16_t ptr; - - /* Pointer */ - r = dns_packet_read_uint8(p, &d, NULL); - if (r < 0) - return r; - - ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d; - if (ptr < DNS_PACKET_HEADER_SIZE || ptr >= jump_barrier) - return -EBADMSG; - - if (after_rindex == 0) - after_rindex = p->rindex; - - /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */ - jump_barrier = ptr; - p->rindex = ptr; - } else - return -EBADMSG; - } - - if (!GREEDY_REALLOC(ret, allocated, n + 1)) - return -ENOMEM; - - ret[n] = 0; - - if (after_rindex != 0) - p->rindex= after_rindex; - - *_ret = ret; - ret = NULL; - - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); - - return 0; -} - -static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *start) { - uint8_t window; - uint8_t length; - const uint8_t *bitmap; - uint8_t bit = 0; - unsigned i; - bool found = false; - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; - int r; - - assert(p); - assert(types); - INIT_REWINDER(rewinder, p); - - r = bitmap_ensure_allocated(types); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &window, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &length, NULL); - if (r < 0) - return r; - - if (length == 0 || length > 32) - return -EBADMSG; - - r = dns_packet_read(p, length, (const void **)&bitmap, NULL); - if (r < 0) - return r; - - for (i = 0; i < length; i++) { - uint8_t bitmask = 1 << 7; - - if (!bitmap[i]) { - found = false; - bit += 8; - continue; - } - - found = true; - - while (bitmask) { - if (bitmap[i] & bitmask) { - uint16_t n; - - n = (uint16_t) window << 8 | (uint16_t) bit; - - /* Ignore pseudo-types. see RFC4034 section 4.1.2 */ - if (dns_type_is_pseudo(n)) - continue; - - r = bitmap_set(*types, n); - if (r < 0) - return r; - } - - bit++; - bitmask >>= 1; - } - } - - if (!found) - return -EBADMSG; - - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); - - return 0; -} - -static int dns_packet_read_type_windows(DnsPacket *p, Bitmap **types, size_t size, size_t *start) { - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; - int r; - - INIT_REWINDER(rewinder, p); - - while (p->rindex < rewinder.saved_rindex + size) { - r = dns_packet_read_type_window(p, types, NULL); - if (r < 0) - return r; - - /* don't read past end of current RR */ - if (p->rindex > rewinder.saved_rindex + size) - return -EBADMSG; - } - - if (p->rindex != rewinder.saved_rindex + size) - return -EBADMSG; - - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); - - return 0; -} - -int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start) { - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; - _cleanup_free_ char *name = NULL; - bool cache_flush = false; - uint16_t class, type; - DnsResourceKey *key; - int r; - - assert(p); - assert(ret); - INIT_REWINDER(rewinder, p); - - r = dns_packet_read_name(p, &name, true, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint16(p, &type, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint16(p, &class, NULL); - if (r < 0) - return r; - - if (p->protocol == DNS_PROTOCOL_MDNS) { - /* See RFC6762, Section 10.2 */ - - if (type != DNS_TYPE_OPT && (class & MDNS_RR_CACHE_FLUSH)) { - class &= ~MDNS_RR_CACHE_FLUSH; - cache_flush = true; - } - } - - key = dns_resource_key_new_consume(class, type, name); - if (!key) - return -ENOMEM; - - name = NULL; - *ret = key; - - if (ret_cache_flush) - *ret_cache_flush = cache_flush; - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); - - return 0; -} - -static bool loc_size_ok(uint8_t size) { - uint8_t m = size >> 4, e = size & 0xF; - - return m <= 9 && e <= 9 && (m > 0 || e == 0); -} - -int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_flush, size_t *start) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; - size_t offset; - uint16_t rdlength; - bool cache_flush; - int r; - - assert(p); - assert(ret); - - INIT_REWINDER(rewinder, p); - - r = dns_packet_read_key(p, &key, &cache_flush, NULL); - if (r < 0) - return r; - - if (!dns_class_is_valid_rr(key->class) || !dns_type_is_valid_rr(key->type)) - return -EBADMSG; - - rr = dns_resource_record_new(key); - if (!rr) - return -ENOMEM; - - r = dns_packet_read_uint32(p, &rr->ttl, NULL); - if (r < 0) - return r; - - /* RFC 2181, Section 8, suggests to - * treat a TTL with the MSB set as a zero TTL. */ - if (rr->ttl & UINT32_C(0x80000000)) - rr->ttl = 0; - - r = dns_packet_read_uint16(p, &rdlength, NULL); - if (r < 0) - return r; - - if (p->rindex + rdlength > p->size) - return -EBADMSG; - - offset = p->rindex; - - switch (rr->key->type) { - - case DNS_TYPE_SRV: - r = dns_packet_read_uint16(p, &rr->srv.priority, NULL); - if (r < 0) - return r; - r = dns_packet_read_uint16(p, &rr->srv.weight, NULL); - if (r < 0) - return r; - r = dns_packet_read_uint16(p, &rr->srv.port, NULL); - if (r < 0) - return r; - r = dns_packet_read_name(p, &rr->srv.name, true, NULL); - break; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - r = dns_packet_read_name(p, &rr->ptr.name, true, NULL); - break; - - case DNS_TYPE_HINFO: - r = dns_packet_read_string(p, &rr->hinfo.cpu, NULL); - if (r < 0) - return r; - - r = dns_packet_read_string(p, &rr->hinfo.os, NULL); - break; - - case DNS_TYPE_SPF: /* exactly the same as TXT */ - case DNS_TYPE_TXT: - if (rdlength <= 0) { - DnsTxtItem *i; - /* RFC 6763, section 6.1 suggests to treat - * empty TXT RRs as equivalent to a TXT record - * with a single empty string. */ - - i = malloc0(offsetof(DnsTxtItem, data) + 1); /* for safety reasons we add an extra NUL byte */ - if (!i) - return -ENOMEM; - - rr->txt.items = i; - } else { - DnsTxtItem *last = NULL; - - while (p->rindex < offset + rdlength) { - DnsTxtItem *i; - const void *data; - size_t sz; - - r = dns_packet_read_raw_string(p, &data, &sz, NULL); - if (r < 0) - return r; - - i = malloc0(offsetof(DnsTxtItem, data) + sz + 1); /* extra NUL byte at the end */ - if (!i) - return -ENOMEM; - - memcpy(i->data, data, sz); - i->length = sz; - - LIST_INSERT_AFTER(items, rr->txt.items, last, i); - last = i; - } - } - - r = 0; - break; - - case DNS_TYPE_A: - r = dns_packet_read_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL); - break; - - case DNS_TYPE_AAAA: - r = dns_packet_read_blob(p, &rr->aaaa.in6_addr, sizeof(struct in6_addr), NULL); - break; - - case DNS_TYPE_SOA: - r = dns_packet_read_name(p, &rr->soa.mname, true, NULL); - if (r < 0) - return r; - - r = dns_packet_read_name(p, &rr->soa.rname, true, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->soa.serial, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->soa.refresh, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->soa.retry, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->soa.expire, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->soa.minimum, NULL); - break; - - case DNS_TYPE_MX: - r = dns_packet_read_uint16(p, &rr->mx.priority, NULL); - if (r < 0) - return r; - - r = dns_packet_read_name(p, &rr->mx.exchange, true, NULL); - break; - - case DNS_TYPE_LOC: { - uint8_t t; - size_t pos; - - r = dns_packet_read_uint8(p, &t, &pos); - if (r < 0) - return r; - - if (t == 0) { - rr->loc.version = t; - - r = dns_packet_read_uint8(p, &rr->loc.size, NULL); - if (r < 0) - return r; - - if (!loc_size_ok(rr->loc.size)) - return -EBADMSG; - - r = dns_packet_read_uint8(p, &rr->loc.horiz_pre, NULL); - if (r < 0) - return r; - - if (!loc_size_ok(rr->loc.horiz_pre)) - return -EBADMSG; - - r = dns_packet_read_uint8(p, &rr->loc.vert_pre, NULL); - if (r < 0) - return r; - - if (!loc_size_ok(rr->loc.vert_pre)) - return -EBADMSG; - - r = dns_packet_read_uint32(p, &rr->loc.latitude, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->loc.longitude, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->loc.altitude, NULL); - if (r < 0) - return r; - - break; - } else { - dns_packet_rewind(p, pos); - rr->unparseable = true; - goto unparseable; - } - } - - case DNS_TYPE_DS: - r = dns_packet_read_uint16(p, &rr->ds.key_tag, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->ds.algorithm, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->ds.digest_type, NULL); - if (r < 0) - return r; - - r = dns_packet_read_memdup(p, rdlength - 4, - &rr->ds.digest, &rr->ds.digest_size, - NULL); - if (r < 0) - return r; - - if (rr->ds.digest_size <= 0) - /* the accepted size depends on the algorithm, but for now - just ensure that the value is greater than zero */ - return -EBADMSG; - - break; - - case DNS_TYPE_SSHFP: - r = dns_packet_read_uint8(p, &rr->sshfp.algorithm, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->sshfp.fptype, NULL); - if (r < 0) - return r; - - r = dns_packet_read_memdup(p, rdlength - 2, - &rr->sshfp.fingerprint, &rr->sshfp.fingerprint_size, - NULL); - - if (rr->sshfp.fingerprint_size <= 0) - /* the accepted size depends on the algorithm, but for now - just ensure that the value is greater than zero */ - return -EBADMSG; - - break; - - case DNS_TYPE_DNSKEY: - r = dns_packet_read_uint16(p, &rr->dnskey.flags, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->dnskey.protocol, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->dnskey.algorithm, NULL); - if (r < 0) - return r; - - r = dns_packet_read_memdup(p, rdlength - 4, - &rr->dnskey.key, &rr->dnskey.key_size, - NULL); - - if (rr->dnskey.key_size <= 0) - /* the accepted size depends on the algorithm, but for now - just ensure that the value is greater than zero */ - return -EBADMSG; - - break; - - case DNS_TYPE_RRSIG: - r = dns_packet_read_uint16(p, &rr->rrsig.type_covered, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->rrsig.algorithm, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->rrsig.labels, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->rrsig.original_ttl, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->rrsig.expiration, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->rrsig.inception, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint16(p, &rr->rrsig.key_tag, NULL); - if (r < 0) - return r; - - r = dns_packet_read_name(p, &rr->rrsig.signer, false, NULL); - if (r < 0) - return r; - - r = dns_packet_read_memdup(p, offset + rdlength - p->rindex, - &rr->rrsig.signature, &rr->rrsig.signature_size, - NULL); - - if (rr->rrsig.signature_size <= 0) - /* the accepted size depends on the algorithm, but for now - just ensure that the value is greater than zero */ - return -EBADMSG; - - break; - - case DNS_TYPE_NSEC: { - - /* - * RFC6762, section 18.14 explictly states mDNS should use name compression. - * This contradicts RFC3845, section 2.1.1 - */ - - bool allow_compressed = p->protocol == DNS_PROTOCOL_MDNS; - - r = dns_packet_read_name(p, &rr->nsec.next_domain_name, allow_compressed, NULL); - if (r < 0) - return r; - - r = dns_packet_read_type_windows(p, &rr->nsec.types, offset + rdlength - p->rindex, NULL); - - /* We accept empty NSEC bitmaps. The bit indicating the presence of the NSEC record itself - * is redundant and in e.g., RFC4956 this fact is used to define a use for NSEC records - * without the NSEC bit set. */ - - break; - } - case DNS_TYPE_NSEC3: { - uint8_t size; - - r = dns_packet_read_uint8(p, &rr->nsec3.algorithm, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->nsec3.flags, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint16(p, &rr->nsec3.iterations, NULL); - if (r < 0) - return r; - - /* this may be zero */ - r = dns_packet_read_uint8(p, &size, NULL); - if (r < 0) - return r; - - r = dns_packet_read_memdup(p, size, &rr->nsec3.salt, &rr->nsec3.salt_size, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &size, NULL); - if (r < 0) - return r; - - if (size <= 0) - return -EBADMSG; - - r = dns_packet_read_memdup(p, size, - &rr->nsec3.next_hashed_name, &rr->nsec3.next_hashed_name_size, - NULL); - if (r < 0) - return r; - - r = dns_packet_read_type_windows(p, &rr->nsec3.types, offset + rdlength - p->rindex, NULL); - - /* empty non-terminals can have NSEC3 records, so empty bitmaps are allowed */ - - break; - } - - case DNS_TYPE_TLSA: - r = dns_packet_read_uint8(p, &rr->tlsa.cert_usage, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->tlsa.selector, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->tlsa.matching_type, NULL); - if (r < 0) - return r; - - r = dns_packet_read_memdup(p, rdlength - 3, - &rr->tlsa.data, &rr->tlsa.data_size, - NULL); - - if (rr->tlsa.data_size <= 0) - /* the accepted size depends on the algorithm, but for now - just ensure that the value is greater than zero */ - return -EBADMSG; - - break; - - case DNS_TYPE_CAA: - r = dns_packet_read_uint8(p, &rr->caa.flags, NULL); - if (r < 0) - return r; - - r = dns_packet_read_string(p, &rr->caa.tag, NULL); - if (r < 0) - return r; - - r = dns_packet_read_memdup(p, - rdlength + offset - p->rindex, - &rr->caa.value, &rr->caa.value_size, NULL); - - break; - - case DNS_TYPE_OPT: /* we only care about the header of OPT for now. */ - case DNS_TYPE_OPENPGPKEY: - default: - unparseable: - r = dns_packet_read_memdup(p, rdlength, &rr->generic.data, &rr->generic.data_size, NULL); - - break; - } - if (r < 0) - return r; - if (p->rindex != offset + rdlength) - return -EBADMSG; - - *ret = rr; - rr = NULL; - - if (ret_cache_flush) - *ret_cache_flush = cache_flush; - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); - - return 0; -} - -static bool opt_is_good(DnsResourceRecord *rr, bool *rfc6975) { - const uint8_t* p; - bool found_dau_dhu_n3u = false; - size_t l; - - /* Checks whether the specified OPT RR is well-formed and whether it contains RFC6975 data (which is not OK in - * a reply). */ - - assert(rr); - assert(rr->key->type == DNS_TYPE_OPT); - - /* Check that the version is 0 */ - if (((rr->ttl >> 16) & UINT32_C(0xFF)) != 0) { - *rfc6975 = false; - return true; /* if it's not version 0, it's OK, but we will ignore the OPT field contents */ - } - - p = rr->opt.data; - l = rr->opt.data_size; - while (l > 0) { - uint16_t option_code, option_length; - - /* At least four bytes for OPTION-CODE and OPTION-LENGTH are required */ - if (l < 4U) - return false; - - option_code = unaligned_read_be16(p); - option_length = unaligned_read_be16(p + 2); - - if (l < option_length + 4U) - return false; - - /* RFC 6975 DAU, DHU or N3U fields found. */ - if (IN_SET(option_code, 5, 6, 7)) - found_dau_dhu_n3u = true; - - p += option_length + 4U; - l -= option_length + 4U; - } - - *rfc6975 = found_dau_dhu_n3u; - return true; -} - -int dns_packet_extract(DnsPacket *p) { - _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = {}; - unsigned n, i; - int r; - - if (p->extracted) - return 0; - - INIT_REWINDER(rewinder, p); - dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE); - - n = DNS_PACKET_QDCOUNT(p); - if (n > 0) { - question = dns_question_new(n); - if (!question) - return -ENOMEM; - - for (i = 0; i < n; i++) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - bool cache_flush; - - r = dns_packet_read_key(p, &key, &cache_flush, NULL); - if (r < 0) - return r; - - if (cache_flush) - return -EBADMSG; - - if (!dns_type_is_valid_query(key->type)) - return -EBADMSG; - - r = dns_question_add(question, key); - if (r < 0) - return r; - } - } - - n = DNS_PACKET_RRCOUNT(p); - if (n > 0) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *previous = NULL; - bool bad_opt = false; - - answer = dns_answer_new(n); - if (!answer) - return -ENOMEM; - - for (i = 0; i < n; i++) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - bool cache_flush = false; - - r = dns_packet_read_rr(p, &rr, &cache_flush, NULL); - if (r < 0) - return r; - - /* Try to reduce memory usage a bit */ - if (previous) - dns_resource_key_reduce(&rr->key, &previous->key); - - if (rr->key->type == DNS_TYPE_OPT) { - bool has_rfc6975; - - if (p->opt || bad_opt) { - /* Multiple OPT RRs? if so, let's ignore all, because there's something wrong - * with the server, and if one is valid we wouldn't know which one. */ - log_debug("Multiple OPT RRs detected, ignoring all."); - bad_opt = true; - continue; - } - - if (!dns_name_is_root(dns_resource_key_name(rr->key))) { - /* If the OPT RR is not owned by the root domain, then it is bad, let's ignore - * it. */ - log_debug("OPT RR is not owned by root domain, ignoring."); - bad_opt = true; - continue; - } - - if (i < DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)) { - /* OPT RR is in the wrong section? Some Belkin routers do this. This is a hint - * the EDNS implementation is borked, like the Belkin one is, hence ignore - * it. */ - log_debug("OPT RR in wrong section, ignoring."); - bad_opt = true; - continue; - } - - if (!opt_is_good(rr, &has_rfc6975)) { - log_debug("Malformed OPT RR, ignoring."); - bad_opt = true; - continue; - } - - if (DNS_PACKET_QR(p)) { - /* Additional checks for responses */ - - if (!DNS_RESOURCE_RECORD_OPT_VERSION_SUPPORTED(rr)) { - /* If this is a reply and we don't know the EDNS version then something - * is weird... */ - log_debug("EDNS version newer that our request, bad server."); - return -EBADMSG; - } - - if (has_rfc6975) { - /* 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; - } - } - - p->opt = dns_resource_record_ref(rr); - } else { - - /* According to RFC 4795, section 2.9. only the RRs from the Answer section shall be - * cached. Hence mark only those RRs as cacheable by default, but not the ones from the - * Additional or Authority sections. */ - - r = dns_answer_add(answer, rr, p->ifindex, - (i < DNS_PACKET_ANCOUNT(p) ? DNS_ANSWER_CACHEABLE : 0) | - (p->protocol == DNS_PROTOCOL_MDNS && !cache_flush ? DNS_ANSWER_SHARED_OWNER : 0)); - if (r < 0) - return r; - } - - /* 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) - p->opt = dns_resource_record_unref(p->opt); - } - - p->question = question; - question = NULL; - - p->answer = answer; - answer = NULL; - - p->extracted = true; - - /* no CANCEL, always rewind */ - return 0; -} - -int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key) { - int r; - - assert(p); - assert(key); - - /* Checks if the specified packet is a reply for the specified - * key and the specified key is the only one in the question - * section. */ - - if (DNS_PACKET_QR(p) != 1) - return 0; - - /* Let's unpack the packet, if that hasn't happened yet. */ - r = dns_packet_extract(p); - if (r < 0) - return r; - - if (p->question->n_keys != 1) - return 0; - - return dns_resource_key_equal(p->question->keys[0], key); -} - -static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = { - [DNS_RCODE_SUCCESS] = "SUCCESS", - [DNS_RCODE_FORMERR] = "FORMERR", - [DNS_RCODE_SERVFAIL] = "SERVFAIL", - [DNS_RCODE_NXDOMAIN] = "NXDOMAIN", - [DNS_RCODE_NOTIMP] = "NOTIMP", - [DNS_RCODE_REFUSED] = "REFUSED", - [DNS_RCODE_YXDOMAIN] = "YXDOMAIN", - [DNS_RCODE_YXRRSET] = "YRRSET", - [DNS_RCODE_NXRRSET] = "NXRRSET", - [DNS_RCODE_NOTAUTH] = "NOTAUTH", - [DNS_RCODE_NOTZONE] = "NOTZONE", - [DNS_RCODE_BADVERS] = "BADVERS", - [DNS_RCODE_BADKEY] = "BADKEY", - [DNS_RCODE_BADTIME] = "BADTIME", - [DNS_RCODE_BADMODE] = "BADMODE", - [DNS_RCODE_BADNAME] = "BADNAME", - [DNS_RCODE_BADALG] = "BADALG", - [DNS_RCODE_BADTRUNC] = "BADTRUNC", - [DNS_RCODE_BADCOOKIE] = "BADCOOKIE", -}; -DEFINE_STRING_TABLE_LOOKUP(dns_rcode, int); - -static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = { - [DNS_PROTOCOL_DNS] = "dns", - [DNS_PROTOCOL_MDNS] = "mdns", - [DNS_PROTOCOL_LLMNR] = "llmnr", -}; -DEFINE_STRING_TABLE_LOOKUP(dns_protocol, DnsProtocol); diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h deleted file mode 100644 index 054dc88a85..0000000000 --- a/src/resolve/resolved-dns-packet.h +++ /dev/null @@ -1,303 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. - ***/ - -#include <netinet/ip.h> -#include <netinet/udp.h> - -#include "hashmap.h" -#include "in-addr-util.h" -#include "macro.h" -#include "sparse-endian.h" - -typedef struct DnsPacketHeader DnsPacketHeader; -typedef struct DnsPacket DnsPacket; - -#include "resolved-def.h" -#include "resolved-dns-answer.h" -#include "resolved-dns-question.h" -#include "resolved-dns-rr.h" - -typedef enum DnsProtocol { - DNS_PROTOCOL_DNS, - DNS_PROTOCOL_MDNS, - DNS_PROTOCOL_LLMNR, - _DNS_PROTOCOL_MAX, - _DNS_PROTOCOL_INVALID = -1 -} DnsProtocol; - -struct DnsPacketHeader { - uint16_t id; - be16_t flags; - be16_t qdcount; - be16_t ancount; - be16_t nscount; - be16_t arcount; -}; - -#define DNS_PACKET_HEADER_SIZE sizeof(DnsPacketHeader) -#define UDP_PACKET_HEADER_SIZE (sizeof(struct iphdr) + sizeof(struct udphdr)) - -/* The various DNS protocols deviate in how large a packet can grow, - but the TCP transport has a 16bit size field, hence that appears to - be the absolute maximum. */ -#define DNS_PACKET_SIZE_MAX 0xFFFF - -/* RFC 1035 say 512 is the maximum, for classic unicast DNS */ -#define DNS_PACKET_UNICAST_SIZE_MAX 512 - -/* With EDNS0 we can use larger packets, default to 4096, which is what is commonly used */ -#define DNS_PACKET_UNICAST_SIZE_LARGE_MAX 4096 - -#define DNS_PACKET_SIZE_START 512 - -struct DnsPacket { - int n_ref; - DnsProtocol protocol; - size_t size, allocated, rindex; - void *_data; /* don't access directly, use DNS_PACKET_DATA()! */ - Hashmap *names; /* For name compression */ - size_t opt_start, opt_size; - - /* Parsed data */ - DnsQuestion *question; - DnsAnswer *answer; - DnsResourceRecord *opt; - - /* Packet reception metadata */ - int ifindex; - int family, ipproto; - union in_addr_union sender, destination; - uint16_t sender_port, destination_port; - uint32_t ttl; - - /* For support of truncated packets */ - DnsPacket *more; - - bool on_stack:1; - bool extracted:1; - bool refuse_compression:1; - bool canonical_form:1; -}; - -static inline uint8_t* DNS_PACKET_DATA(DnsPacket *p) { - if (_unlikely_(!p)) - return NULL; - - if (p->_data) - return p->_data; - - return ((uint8_t*) p) + ALIGN(sizeof(DnsPacket)); -} - -#define DNS_PACKET_HEADER(p) ((DnsPacketHeader*) DNS_PACKET_DATA(p)) -#define DNS_PACKET_ID(p) DNS_PACKET_HEADER(p)->id -#define DNS_PACKET_QR(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 15) & 1) -#define DNS_PACKET_OPCODE(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 11) & 15) -#define DNS_PACKET_AA(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 10) & 1) -#define DNS_PACKET_TC(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 9) & 1) -#define DNS_PACKET_RD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 8) & 1) -#define DNS_PACKET_RA(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 7) & 1) -#define DNS_PACKET_AD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 5) & 1) -#define DNS_PACKET_CD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 4) & 1) - -#define DNS_PACKET_FLAG_TC (UINT16_C(1) << 9) - -static inline uint16_t DNS_PACKET_RCODE(DnsPacket *p) { - uint16_t rcode; - - if (p->opt) - rcode = (uint16_t) (p->opt->ttl >> 24); - else - rcode = 0; - - return rcode | (be16toh(DNS_PACKET_HEADER(p)->flags) & 0xF); -} - -static inline uint16_t DNS_PACKET_PAYLOAD_SIZE_MAX(DnsPacket *p) { - - /* Returns the advertised maximum datagram size for replies, or the DNS default if there's nothing defined. */ - - if (p->opt) - return MAX(DNS_PACKET_UNICAST_SIZE_MAX, p->opt->key->class); - - return DNS_PACKET_UNICAST_SIZE_MAX; -} - -static inline bool DNS_PACKET_DO(DnsPacket *p) { - if (!p->opt) - return false; - - return !!(p->opt->ttl & (1U << 15)); -} - -static inline bool DNS_PACKET_VERSION_SUPPORTED(DnsPacket *p) { - /* Returns true if this packet is in a version we support. Which means either non-EDNS or EDNS(0), but not EDNS - * of any newer versions */ - - if (!p->opt) - return true; - - return DNS_RESOURCE_RECORD_OPT_VERSION_SUPPORTED(p->opt); -} - -/* LLMNR defines some bits differently */ -#define DNS_PACKET_LLMNR_C(p) DNS_PACKET_AA(p) -#define DNS_PACKET_LLMNR_T(p) DNS_PACKET_RD(p) - -#define DNS_PACKET_QDCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->qdcount) -#define DNS_PACKET_ANCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->ancount) -#define DNS_PACKET_NSCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->nscount) -#define DNS_PACKET_ARCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->arcount) - -#define DNS_PACKET_MAKE_FLAGS(qr, opcode, aa, tc, rd, ra, ad, cd, rcode) \ - (((uint16_t) !!(qr) << 15) | \ - ((uint16_t) ((opcode) & 15) << 11) | \ - ((uint16_t) !!(aa) << 10) | /* on LLMNR: c */ \ - ((uint16_t) !!(tc) << 9) | \ - ((uint16_t) !!(rd) << 8) | /* on LLMNR: t */ \ - ((uint16_t) !!(ra) << 7) | \ - ((uint16_t) !!(ad) << 5) | \ - ((uint16_t) !!(cd) << 4) | \ - ((uint16_t) ((rcode) & 15))) - -static inline unsigned DNS_PACKET_RRCOUNT(DnsPacket *p) { - return - (unsigned) DNS_PACKET_ANCOUNT(p) + - (unsigned) DNS_PACKET_NSCOUNT(p) + - (unsigned) DNS_PACKET_ARCOUNT(p); -} - -int dns_packet_new(DnsPacket **p, DnsProtocol protocol, size_t mtu); -int dns_packet_new_query(DnsPacket **p, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled); - -void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated); - -DnsPacket *dns_packet_ref(DnsPacket *p); -DnsPacket *dns_packet_unref(DnsPacket *p); - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsPacket*, dns_packet_unref); - -int dns_packet_validate(DnsPacket *p); -int dns_packet_validate_reply(DnsPacket *p); -int dns_packet_validate_query(DnsPacket *p); - -int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key); - -int dns_packet_append_blob(DnsPacket *p, const void *d, size_t sz, size_t *start); -int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start); -int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start); -int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start); -int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start); -int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start); -int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, bool canonical_candidate, size_t *start); -int dns_packet_append_name(DnsPacket *p, const char *name, bool allow_compression, bool canonical_candidate, size_t *start); -int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *key, size_t *start); -int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start); -int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, int rcode, size_t *start); -int dns_packet_append_question(DnsPacket *p, DnsQuestion *q); -int dns_packet_append_answer(DnsPacket *p, DnsAnswer *a); - -void dns_packet_truncate(DnsPacket *p, size_t sz); -int dns_packet_truncate_opt(DnsPacket *p); - -int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start); -int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start); -int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start); -int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start); -int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start); -int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start); -int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start); -int dns_packet_read_name(DnsPacket *p, char **ret, bool allow_compression, size_t *start); -int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start); -int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_flush, size_t *start); - -void dns_packet_rewind(DnsPacket *p, size_t idx); - -int dns_packet_skip_question(DnsPacket *p); -int dns_packet_extract(DnsPacket *p); - -static inline bool DNS_PACKET_SHALL_CACHE(DnsPacket *p) { - /* Never cache data originating from localhost, under the - * assumption, that it's coming from a locally DNS forwarder - * or server, that is caching on its own. */ - - return in_addr_is_localhost(p->family, &p->sender) == 0; -} - -/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 */ -enum { - DNS_RCODE_SUCCESS = 0, - DNS_RCODE_FORMERR = 1, - DNS_RCODE_SERVFAIL = 2, - DNS_RCODE_NXDOMAIN = 3, - DNS_RCODE_NOTIMP = 4, - DNS_RCODE_REFUSED = 5, - DNS_RCODE_YXDOMAIN = 6, - DNS_RCODE_YXRRSET = 7, - DNS_RCODE_NXRRSET = 8, - DNS_RCODE_NOTAUTH = 9, - DNS_RCODE_NOTZONE = 10, - DNS_RCODE_BADVERS = 16, - DNS_RCODE_BADSIG = 16, /* duplicate value! */ - DNS_RCODE_BADKEY = 17, - DNS_RCODE_BADTIME = 18, - DNS_RCODE_BADMODE = 19, - DNS_RCODE_BADNAME = 20, - DNS_RCODE_BADALG = 21, - DNS_RCODE_BADTRUNC = 22, - DNS_RCODE_BADCOOKIE = 23, - _DNS_RCODE_MAX_DEFINED, - _DNS_RCODE_MAX = 4095 /* 4 bit rcode in the header plus 8 bit rcode in OPT, makes 12 bit */ -}; - -const char* dns_rcode_to_string(int i) _const_; -int dns_rcode_from_string(const char *s) _pure_; - -const char* dns_protocol_to_string(DnsProtocol p) _const_; -DnsProtocol dns_protocol_from_string(const char *s) _pure_; - -#define LLMNR_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 252U) }) -#define LLMNR_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03 } }) - -#define MDNS_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 251U) }) -#define MDNS_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb } }) - -static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family, bool authenticated) { - uint64_t f; - - /* Converts a protocol + family into a flags field as used in queries and responses */ - - f = authenticated ? SD_RESOLVED_AUTHENTICATED : 0; - - switch (protocol) { - case DNS_PROTOCOL_DNS: - return f|SD_RESOLVED_DNS; - - case DNS_PROTOCOL_LLMNR: - return f|(family == AF_INET6 ? SD_RESOLVED_LLMNR_IPV6 : SD_RESOLVED_LLMNR_IPV4); - - case DNS_PROTOCOL_MDNS: - return f|(family == AF_INET6 ? SD_RESOLVED_MDNS_IPV6 : SD_RESOLVED_MDNS_IPV4); - - default: - return f; - } -} diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c deleted file mode 100644 index e03db4d003..0000000000 --- a/src/resolve/resolved-dns-query.c +++ /dev/null @@ -1,1117 +0,0 @@ -/*** - 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 "dns-domain.h" -#include "dns-type.h" -#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 */ -#define QUERY_TIMEOUT_USEC (30 * USEC_PER_SEC) - -#define CNAME_MAX 8 -#define QUERIES_MAX 2048 -#define AUXILIARY_QUERIES_MAX 64 - -static int dns_query_candidate_new(DnsQueryCandidate **ret, DnsQuery *q, DnsScope *s) { - DnsQueryCandidate *c; - - assert(ret); - assert(q); - assert(s); - - c = new0(DnsQueryCandidate, 1); - if (!c) - return -ENOMEM; - - c->query = q; - c->scope = s; - - LIST_PREPEND(candidates_by_query, q->candidates, c); - LIST_PREPEND(candidates_by_scope, s->query_candidates, c); - - *ret = c; - return 0; -} - -static void dns_query_candidate_stop(DnsQueryCandidate *c) { - DnsTransaction *t; - - assert(c); - - while ((t = set_steal_first(c->transactions))) { - set_remove(t->notify_query_candidates, c); - set_remove(t->notify_query_candidates_done, c); - dns_transaction_gc(t); - } -} - -DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c) { - - if (!c) - return NULL; - - dns_query_candidate_stop(c); - - set_free(c->transactions); - dns_search_domain_unref(c->search_domain); - - if (c->query) - LIST_REMOVE(candidates_by_query, c->query->candidates, c); - - if (c->scope) - LIST_REMOVE(candidates_by_scope, c->scope->query_candidates, c); - - return mfree(c); -} - -static int dns_query_candidate_next_search_domain(DnsQueryCandidate *c) { - DnsSearchDomain *next = NULL; - - assert(c); - - if (c->search_domain && c->search_domain->linked) - next = c->search_domain->domains_next; - else - next = dns_scope_get_search_domains(c->scope); - - for (;;) { - if (!next) /* We hit the end of the list */ - return 0; - - if (!next->route_only) - break; - - /* Skip over route-only domains */ - next = next->domains_next; - } - - dns_search_domain_unref(c->search_domain); - c->search_domain = dns_search_domain_ref(next); - - return 1; -} - -static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResourceKey *key) { - DnsTransaction *t; - int r; - - assert(c); - assert(key); - - t = dns_scope_find_transaction(c->scope, key, true); - if (!t) { - r = dns_transaction_new(&t, c->scope, key); - if (r < 0) - return r; - } else { - if (set_contains(c->transactions, t)) - return 0; - } - - r = set_ensure_allocated(&c->transactions, NULL); - if (r < 0) - goto gc; - - r = set_ensure_allocated(&t->notify_query_candidates, NULL); - if (r < 0) - goto gc; - - r = set_ensure_allocated(&t->notify_query_candidates_done, NULL); - if (r < 0) - goto gc; - - r = set_put(t->notify_query_candidates, c); - if (r < 0) - goto gc; - - r = set_put(c->transactions, t); - if (r < 0) { - (void) set_remove(t->notify_query_candidates, c); - goto gc; - } - - t->clamp_ttl = c->query->clamp_ttl; - return 1; - -gc: - dns_transaction_gc(t); - return r; -} - -static int dns_query_candidate_go(DnsQueryCandidate *c) { - DnsTransaction *t; - Iterator i; - int r; - unsigned n = 0; - - assert(c); - - /* Start the transactions that are not started yet */ - SET_FOREACH(t, c->transactions, i) { - if (t->state != DNS_TRANSACTION_NULL) - continue; - - r = dns_transaction_go(t); - if (r < 0) - return r; - - n++; - } - - /* If there was nothing to start, then let's proceed immediately */ - if (n == 0) - dns_query_candidate_notify(c); - - return 0; -} - -static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) { - DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; - DnsTransaction *t; - Iterator i; - - assert(c); - - if (c->error_code != 0) - return DNS_TRANSACTION_ERRNO; - - SET_FOREACH(t, c->transactions, i) { - - switch (t->state) { - - case DNS_TRANSACTION_NULL: - /* If there's a NULL transaction pending, then - * this means not all transactions where - * started yet, and we were called from within - * the stackframe that is supposed to start - * remaining transactions. In this case, - * simply claim the candidate is pending. */ - - case DNS_TRANSACTION_PENDING: - case DNS_TRANSACTION_VALIDATING: - /* If there's one transaction currently in - * VALIDATING state, then this means there's - * also one in PENDING state, hence we can - * return PENDING immediately. */ - return DNS_TRANSACTION_PENDING; - - case DNS_TRANSACTION_SUCCESS: - state = t->state; - break; - - default: - if (state != DNS_TRANSACTION_SUCCESS) - state = t->state; - - break; - } - } - - return state; -} - -static bool dns_query_candidate_is_routable(DnsQueryCandidate *c, uint16_t type) { - int family; - - assert(c); - - /* Checks whether the specified RR type matches an address family that is routable on the link(s) the scope of - * this candidate belongs to. Specifically, whether there's a routable IPv4 address on it if we query an A RR, - * or a routable IPv6 address if we query an AAAA RR. */ - - if (!c->query->suppress_unroutable_family) - return true; - - if (c->scope->protocol != DNS_PROTOCOL_DNS) - return true; - - family = dns_type_to_af(type); - if (family < 0) - return true; - - if (c->scope->link) - return link_relevant(c->scope->link, family, false); - else - return manager_routable(c->scope->manager, family); -} - -static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) { - DnsQuestion *question; - DnsResourceKey *key; - int n = 0, r; - - assert(c); - - dns_query_candidate_stop(c); - - question = dns_query_question_for_protocol(c->query, c->scope->protocol); - - /* Create one transaction per question key */ - DNS_QUESTION_FOREACH(key, question) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *new_key = NULL; - DnsResourceKey *qkey; - - if (!dns_query_candidate_is_routable(c, key->type)) - continue; - - if (c->search_domain) { - r = dns_resource_key_new_append_suffix(&new_key, key, c->search_domain->name); - if (r < 0) - goto fail; - - qkey = new_key; - } else - qkey = key; - - if (!dns_scope_good_key(c->scope, qkey)) - continue; - - r = dns_query_candidate_add_transaction(c, qkey); - if (r < 0) - goto fail; - - n++; - } - - return n; - -fail: - dns_query_candidate_stop(c); - return r; -} - -void dns_query_candidate_notify(DnsQueryCandidate *c) { - DnsTransactionState state; - int r; - - assert(c); - - state = dns_query_candidate_state(c); - - if (DNS_TRANSACTION_IS_LIVE(state)) - return; - - if (state != DNS_TRANSACTION_SUCCESS && c->search_domain) { - - r = dns_query_candidate_next_search_domain(c); - if (r < 0) - goto fail; - - if (r > 0) { - /* OK, there's another search domain to try, let's do so. */ - - r = dns_query_candidate_setup_transactions(c); - if (r < 0) - goto fail; - - if (r > 0) { - /* New transactions where queued. Start them and wait */ - - r = dns_query_candidate_go(c); - if (r < 0) - goto fail; - - return; - } - } - - } - - dns_query_ready(c->query); - return; - -fail: - log_warning_errno(r, "Failed to follow search domains: %m"); - c->error_code = r; - dns_query_ready(c->query); -} - -static void dns_query_stop(DnsQuery *q) { - DnsQueryCandidate *c; - - assert(q); - - q->timeout_event_source = sd_event_source_unref(q->timeout_event_source); - - LIST_FOREACH(candidates_by_query, c, q->candidates) - dns_query_candidate_stop(c); -} - -static void dns_query_free_candidates(DnsQuery *q) { - assert(q); - - while (q->candidates) - dns_query_candidate_free(q->candidates); -} - -static void dns_query_reset_answer(DnsQuery *q) { - assert(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; - q->answer_search_domain = dns_search_domain_unref(q->answer_search_domain); -} - -DnsQuery *dns_query_free(DnsQuery *q) { - if (!q) - return NULL; - - while (q->auxiliary_queries) - dns_query_free(q->auxiliary_queries); - - if (q->auxiliary_for) { - assert(q->auxiliary_for->n_auxiliary_queries > 0); - q->auxiliary_for->n_auxiliary_queries--; - LIST_REMOVE(auxiliary_queries, q->auxiliary_for->auxiliary_queries, q); - } - - dns_query_free_candidates(q); - - dns_question_unref(q->question_idna); - dns_question_unref(q->question_utf8); - - dns_query_reset_answer(q); - - sd_bus_message_unref(q->request); - sd_bus_track_unref(q->bus_track); - - dns_packet_unref(q->request_dns_packet); - - if (q->request_dns_stream) { - /* Detach the stream from our query, in case something else keeps a reference to it. */ - q->request_dns_stream->complete = NULL; - q->request_dns_stream->on_packet = NULL; - q->request_dns_stream->query = NULL; - dns_stream_unref(q->request_dns_stream); - } - - free(q->request_address_string); - - if (q->manager) { - LIST_REMOVE(queries, q->manager->dns_queries, q); - q->manager->n_dns_queries--; - } - - return mfree(q); -} - -int dns_query_new( - Manager *m, - DnsQuery **ret, - DnsQuestion *question_utf8, - DnsQuestion *question_idna, - int ifindex, - uint64_t flags) { - - _cleanup_(dns_query_freep) DnsQuery *q = NULL; - DnsResourceKey *key; - bool good = false; - int r; - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - - assert(m); - - if (dns_question_size(question_utf8) > 0) { - r = dns_question_is_valid_for_query(question_utf8); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - good = true; - } - - /* If the IDNA and UTF8 questions are the same, merge their references */ - r = dns_question_is_equal(question_idna, question_utf8); - if (r < 0) - return r; - if (r > 0) - question_idna = question_utf8; - else { - if (dns_question_size(question_idna) > 0) { - r = dns_question_is_valid_for_query(question_idna); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - good = true; - } - } - - if (!good) /* don't allow empty queries */ - return -EINVAL; - - if (m->n_dns_queries >= QUERIES_MAX) - return -EBUSY; - - q = new0(DnsQuery, 1); - if (!q) - return -ENOMEM; - - q->question_utf8 = dns_question_ref(question_utf8); - q->question_idna = dns_question_ref(question_idna); - q->ifindex = ifindex; - q->flags = flags; - q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; - q->answer_protocol = _DNS_PROTOCOL_INVALID; - q->answer_family = AF_UNSPEC; - - /* First dump UTF8 question */ - DNS_QUESTION_FOREACH(key, question_utf8) - log_debug("Looking up RR for %s.", - dns_resource_key_to_string(key, key_str, sizeof key_str)); - - /* And then dump the IDNA question, but only what hasn't been dumped already through the UTF8 question. */ - DNS_QUESTION_FOREACH(key, question_idna) { - r = dns_question_contains(question_utf8, key); - if (r < 0) - return r; - if (r > 0) - continue; - - log_debug("Looking up IDNA RR for %s.", - dns_resource_key_to_string(key, key_str, sizeof key_str)); - } - - LIST_PREPEND(queries, m->dns_queries, q); - m->n_dns_queries++; - q->manager = m; - - if (ret) - *ret = q; - q = NULL; - - return 0; -} - -int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for) { - assert(q); - assert(auxiliary_for); - - /* Ensure that the query is not auxiliary yet, and - * nothing else is auxiliary to it either */ - assert(!q->auxiliary_for); - assert(!q->auxiliary_queries); - - /* Ensure that the unit we shall be made auxiliary for isn't - * auxiliary itself */ - assert(!auxiliary_for->auxiliary_for); - - if (auxiliary_for->n_auxiliary_queries >= AUXILIARY_QUERIES_MAX) - return -EAGAIN; - - LIST_PREPEND(auxiliary_queries, auxiliary_for->auxiliary_queries, q); - q->auxiliary_for = auxiliary_for; - - auxiliary_for->n_auxiliary_queries++; - return 0; -} - -static void dns_query_complete(DnsQuery *q, DnsTransactionState state) { - assert(q); - assert(!DNS_TRANSACTION_IS_LIVE(state)); - assert(DNS_TRANSACTION_IS_LIVE(q->state)); - - /* Note that this call might invalidate the query. Callers - * should hence not attempt to access the query or transaction - * after calling this function. */ - - q->state = state; - - dns_query_stop(q); - if (q->complete) - q->complete(q); -} - -static int on_query_timeout(sd_event_source *s, usec_t usec, void *userdata) { - DnsQuery *q = userdata; - - assert(s); - assert(q); - - dns_query_complete(q, DNS_TRANSACTION_TIMEOUT); - return 0; -} - -static int dns_query_add_candidate(DnsQuery *q, DnsScope *s) { - DnsQueryCandidate *c; - int r; - - assert(q); - assert(s); - - r = dns_query_candidate_new(&c, q, s); - if (r < 0) - return r; - - /* If this a single-label domain on DNS, we might append a suitable search domain first. */ - if ((q->flags & SD_RESOLVED_NO_SEARCH) == 0) { - r = dns_scope_name_needs_search_domain(s, dns_question_first_name(q->question_idna)); - if (r < 0) - goto fail; - if (r > 0) { - /* OK, we need a search domain now. Let's find one for this scope */ - - r = dns_query_candidate_next_search_domain(c); - if (r <= 0) /* if there's no search domain, then we won't add any transaction. */ - goto fail; - } - } - - r = dns_query_candidate_setup_transactions(c); - if (r < 0) - goto fail; - - return 0; - -fail: - dns_query_candidate_free(c); - return r; -} - -static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - int r; - - assert(q); - assert(state); - - /* 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_NETWORK_DOWN, - DNS_TRANSACTION_NOT_FOUND)) - return 0; - - r = dns_synthesize_answer( - q->manager, - q->question_utf8, - q->ifindex, - &answer); - - if (r <= 0) - return r; - - dns_query_reset_answer(q); - - 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; - - *state = DNS_TRANSACTION_SUCCESS; - - return 1; -} - -static int dns_query_try_etc_hosts(DnsQuery *q) { - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - int r; - - assert(q); - - /* 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 = manager_etc_hosts_lookup( - q->manager, - q->question_utf8, - &answer); - if (r <= 0) - return r; - - dns_query_reset_answer(q); - - 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; - - return 1; -} - -int dns_query_go(DnsQuery *q) { - DnsScopeMatch found = DNS_SCOPE_NO; - DnsScope *s, *first = NULL; - DnsQueryCandidate *c; - int r; - - assert(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; - - name = dns_question_first_name(dns_query_question_for_protocol(q, s->protocol)); - if (!name) - continue; - - match = dns_scope_good_domain(s, q->ifindex, q->flags, name); - if (match < 0) - return match; - - if (match == DNS_SCOPE_NO) - continue; - - found = match; - - if (match == DNS_SCOPE_YES) { - first = s; - break; - } else { - assert(match == DNS_SCOPE_MAYBE); - - if (!first) - first = s; - } - } - - if (found == DNS_SCOPE_NO) { - DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; - - r = dns_query_synthesize_reply(q, &state); - if (r < 0) - return r; - - dns_query_complete(q, state); - return 1; - } - - r = dns_query_add_candidate(q, first); - if (r < 0) - goto fail; - - LIST_FOREACH(scopes, s, first->scopes_next) { - DnsScopeMatch match; - const char *name; - - name = dns_question_first_name(dns_query_question_for_protocol(q, s->protocol)); - if (!name) - continue; - - match = dns_scope_good_domain(s, q->ifindex, q->flags, name); - if (match < 0) - goto fail; - - if (match != found) - continue; - - r = dns_query_add_candidate(q, s); - if (r < 0) - goto fail; - } - - dns_query_reset_answer(q); - - r = sd_event_add_time( - q->manager->event, - &q->timeout_event_source, - clock_boottime_or_monotonic(), - now(clock_boottime_or_monotonic()) + QUERY_TIMEOUT_USEC, 0, - on_query_timeout, q); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(q->timeout_event_source, "query-timeout"); - - q->state = DNS_TRANSACTION_PENDING; - q->block_ready++; - - /* Start the transactions */ - LIST_FOREACH(candidates_by_query, c, q->candidates) { - r = dns_query_candidate_go(c); - if (r < 0) { - q->block_ready--; - goto fail; - } - } - - q->block_ready--; - dns_query_ready(q); - - return 1; - -fail: - dns_query_stop(q); - return r; -} - -static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { - DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; - bool has_authenticated = false, has_non_authenticated = false; - DnssecResult dnssec_result_authenticated = _DNSSEC_RESULT_INVALID, dnssec_result_non_authenticated = _DNSSEC_RESULT_INVALID; - DnsTransaction *t; - Iterator i; - int r; - - assert(q); - - if (!c) { - 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) { - - case DNS_TRANSACTION_SUCCESS: { - /* We found a successfully reply, merge it into the answer */ - r = dns_answer_extend(&q->answer, t->answer); - if (r < 0) - goto fail; - - q->answer_rcode = t->answer_rcode; - q->answer_errno = 0; - - if (t->answer_authenticated) { - has_authenticated = true; - dnssec_result_authenticated = t->answer_dnssec_result; - } else { - has_non_authenticated = true; - dnssec_result_non_authenticated = t->answer_dnssec_result; - } - - state = DNS_TRANSACTION_SUCCESS; - break; - } - - case DNS_TRANSACTION_NULL: - case DNS_TRANSACTION_PENDING: - case DNS_TRANSACTION_VALIDATING: - case DNS_TRANSACTION_ABORTED: - /* Ignore transactions that didn't complete */ - continue; - - default: - /* Any kind of failure? Store the data away, - * if there's nothing stored yet. */ - - if (state == DNS_TRANSACTION_SUCCESS) - continue; - - 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; - } - } - - if (state == DNS_TRANSACTION_SUCCESS) { - q->answer_authenticated = has_authenticated && !has_non_authenticated; - q->answer_dnssec_result = q->answer_authenticated ? dnssec_result_authenticated : dnssec_result_non_authenticated; - } - - q->answer_protocol = c->scope->protocol; - q->answer_family = c->scope->family; - - dns_search_domain_unref(q->answer_search_domain); - q->answer_search_domain = dns_search_domain_ref(c->search_domain); - - 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) { - - DnsQueryCandidate *bad = NULL, *c; - bool pending = false; - - assert(q); - assert(DNS_TRANSACTION_IS_LIVE(q->state)); - - /* Note that this call might invalidate the query. Callers - * should hence not attempt to access the query or transaction - * after calling this function, unless the block_ready - * counter was explicitly bumped before doing so. */ - - if (q->block_ready > 0) - return; - - LIST_FOREACH(candidates_by_query, c, q->candidates) { - DnsTransactionState state; - - state = dns_query_candidate_state(c); - switch (state) { - - case DNS_TRANSACTION_SUCCESS: - /* One of the candidates is successful, - * let's use it, and copy its data out */ - dns_query_accept(q, c); - return; - - case DNS_TRANSACTION_NULL: - case DNS_TRANSACTION_PENDING: - case DNS_TRANSACTION_VALIDATING: - /* One of the candidates is still going on, - * let's maybe wait for it */ - pending = true; - break; - - default: - /* Any kind of failure */ - bad = c; - break; - } - } - - if (pending) - return; - - dns_query_accept(q, bad); -} - -static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) { - _cleanup_(dns_question_unrefp) DnsQuestion *nq_idna = NULL, *nq_utf8 = NULL; - int r, k; - - assert(q); - - q->n_cname_redirects++; - if (q->n_cname_redirects > CNAME_MAX) - return -ELOOP; - - r = dns_question_cname_redirect(q->question_idna, cname, &nq_idna); - if (r < 0) - return r; - else if (r > 0) - log_debug("Following CNAME/DNAME %s → %s.", dns_question_first_name(q->question_idna), dns_question_first_name(nq_idna)); - - k = dns_question_is_equal(q->question_idna, q->question_utf8); - if (k < 0) - return r; - if (k > 0) { - /* Same question? Shortcut new question generation */ - nq_utf8 = dns_question_ref(nq_idna); - k = r; - } else { - k = dns_question_cname_redirect(q->question_utf8, cname, &nq_utf8); - if (k < 0) - return k; - else if (k > 0) - log_debug("Following UTF8 CNAME/DNAME %s → %s.", dns_question_first_name(q->question_utf8), dns_question_first_name(nq_utf8)); - } - - if (r == 0 && k == 0) /* No actual cname happened? */ - return -ELOOP; - - if (q->answer_protocol == DNS_PROTOCOL_DNS) { - /* Don't permit CNAME redirects from unicast DNS to LLMNR or MulticastDNS, so that global resources - * cannot invade the local namespace. The opposite way we permit: local names may redirect to global - * ones. */ - - q->flags &= ~(SD_RESOLVED_LLMNR|SD_RESOLVED_MDNS); /* mask away the local protocols */ - } - - /* Turn off searching for the new name */ - q->flags |= SD_RESOLVED_NO_SEARCH; - - dns_question_unref(q->question_idna); - q->question_idna = nq_idna; - nq_idna = NULL; - - dns_question_unref(q->question_utf8); - q->question_utf8 = nq_utf8; - nq_utf8 = NULL; - - dns_query_free_candidates(q); - dns_query_reset_answer(q); - - q->state = DNS_TRANSACTION_NULL; - - return 0; -} - -int dns_query_process_cname(DnsQuery *q) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *cname = NULL; - DnsQuestion *question; - DnsResourceRecord *rr; - int r; - - assert(q); - - if (!IN_SET(q->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_NULL)) - return DNS_QUERY_NOMATCH; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - - DNS_ANSWER_FOREACH(rr, q->answer) { - r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); - if (r < 0) - return r; - if (r > 0) - return DNS_QUERY_MATCH; /* The answer matches directly, no need to follow cnames */ - - r = dns_question_matches_cname_or_dname(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); - if (r < 0) - return r; - if (r > 0 && !cname) - cname = dns_resource_record_ref(rr); - } - - if (!cname) - return DNS_QUERY_NOMATCH; /* No match and no cname to follow */ - - if (q->flags & SD_RESOLVED_NO_CNAME) - return -ELOOP; - - /* OK, let's actually follow the CNAME */ - r = dns_query_cname_redirect(q, cname); - if (r < 0) - return r; - - /* Let's see if the answer can already answer the new - * redirected question */ - r = dns_query_process_cname(q); - if (r != DNS_QUERY_NOMATCH) - return r; - - /* OK, it cannot, let's begin with the new query */ - r = dns_query_go(q); - if (r < 0) - return r; - - return DNS_QUERY_RESTARTED; /* We restarted the query for a new cname */ -} - -static int on_bus_track(sd_bus_track *t, void *userdata) { - DnsQuery *q = userdata; - - assert(t); - assert(q); - - log_debug("Client of active query vanished, aborting query."); - dns_query_complete(q, DNS_TRANSACTION_ABORTED); - return 0; -} - -int dns_query_bus_track(DnsQuery *q, sd_bus_message *m) { - int r; - - assert(q); - assert(m); - - if (!q->bus_track) { - r = sd_bus_track_new(sd_bus_message_get_bus(m), &q->bus_track, on_bus_track, q); - if (r < 0) - return r; - } - - r = sd_bus_track_add_sender(q->bus_track, m); - if (r < 0) - return r; - - return 0; -} - -DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol) { - assert(q); - - switch (protocol) { - - case DNS_PROTOCOL_DNS: - return q->question_idna; - - case DNS_PROTOCOL_MDNS: - case DNS_PROTOCOL_LLMNR: - return q->question_utf8; - - default: - return NULL; - } -} - -const char *dns_query_string(DnsQuery *q) { - const char *name; - int r; - - /* Returns a somewhat useful human-readable lookup key string for this query */ - - if (q->request_address_string) - return q->request_address_string; - - if (q->request_address_valid) { - r = in_addr_to_string(q->request_family, &q->request_address, &q->request_address_string); - if (r >= 0) - return q->request_address_string; - } - - name = dns_question_first_name(q->question_utf8); - if (name) - return name; - - return dns_question_first_name(q->question_idna); -} diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h deleted file mode 100644 index 49a35b846b..0000000000 --- a/src/resolve/resolved-dns-query.h +++ /dev/null @@ -1,141 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - - -#include "sd-bus.h" - -#include "set.h" - -typedef struct DnsQueryCandidate DnsQueryCandidate; -typedef struct DnsQuery DnsQuery; - -#include "resolved-dns-answer.h" -#include "resolved-dns-question.h" -#include "resolved-dns-stream.h" -#include "resolved-dns-search-domain.h" - -struct DnsQueryCandidate { - DnsQuery *query; - DnsScope *scope; - - DnsSearchDomain *search_domain; - - int error_code; - Set *transactions; - - LIST_FIELDS(DnsQueryCandidate, candidates_by_query); - LIST_FIELDS(DnsQueryCandidate, candidates_by_scope); -}; - -struct DnsQuery { - Manager *manager; - - /* When resolving a service, we first create a TXT+SRV query, - * and then for the hostnames we discover auxiliary A+AAAA - * queries. This pointer always points from the auxiliary - * queries back to the TXT+SRV query. */ - DnsQuery *auxiliary_for; - LIST_HEAD(DnsQuery, auxiliary_queries); - unsigned n_auxiliary_queries; - int auxiliary_result; - - /* The question, formatted in IDNA for use on classic DNS, and as UTF8 for use in LLMNR or mDNS. Note that even - * on classic DNS some labels might use UTF8 encoding. Specifically, DNS-SD service names (in contrast to their - * domain suffixes) use UTF-8 encoding even on DNS. Thus, the difference between these two fields is mostly - * relevant only for explicit *hostname* lookups as well as the domain suffixes of service lookups. */ - DnsQuestion *question_idna; - DnsQuestion *question_utf8; - - uint64_t flags; - int ifindex; - - /* If true, A or AAAA RR lookups will be suppressed on links with no routable address of the matching address - * family */ - bool suppress_unroutable_family; - - - /* If true, the RR TTLs of the answer will be clamped by their current left validity in the cache */ - bool clamp_ttl; - - DnsTransactionState state; - unsigned n_cname_redirects; - - LIST_HEAD(DnsQueryCandidate, candidates); - sd_event_source *timeout_event_source; - - /* Discovered data */ - DnsAnswer *answer; - int answer_rcode; - DnssecResult answer_dnssec_result; - bool answer_authenticated; - 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; - int request_family; - bool request_address_valid; - union in_addr_union request_address; - unsigned block_all_complete; - char *request_address_string; - - /* DNS stub information */ - DnsPacket *request_dns_packet; - DnsStream *request_dns_stream; - - /* Completion callback */ - void (*complete)(DnsQuery* q); - unsigned block_ready; - - sd_bus_track *bus_track; - - LIST_FIELDS(DnsQuery, queries); - LIST_FIELDS(DnsQuery, auxiliary_queries); -}; - -enum { - DNS_QUERY_MATCH, - DNS_QUERY_NOMATCH, - DNS_QUERY_RESTARTED, -}; - -DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c); -void dns_query_candidate_notify(DnsQueryCandidate *c); - -int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question_utf8, DnsQuestion *question_idna, int family, uint64_t flags); -DnsQuery *dns_query_free(DnsQuery *q); - -int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for); - -int dns_query_go(DnsQuery *q); -void dns_query_ready(DnsQuery *q); - -int dns_query_process_cname(DnsQuery *q); - -int dns_query_bus_track(DnsQuery *q, sd_bus_message *m); - -DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol); - -const char *dns_query_string(DnsQuery *q); - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuery*, dns_query_free); diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c deleted file mode 100644 index c8b502d1cd..0000000000 --- a/src/resolve/resolved-dns-question.c +++ /dev/null @@ -1,468 +0,0 @@ -/*** - 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 "dns-domain.h" -#include "dns-type.h" -#include "resolved-dns-question.h" - -DnsQuestion *dns_question_new(unsigned n) { - DnsQuestion *q; - - assert(n > 0); - - q = malloc0(offsetof(DnsQuestion, keys) + sizeof(DnsResourceKey*) * n); - if (!q) - return NULL; - - q->n_ref = 1; - q->n_allocated = n; - - return q; -} - -DnsQuestion *dns_question_ref(DnsQuestion *q) { - if (!q) - return NULL; - - assert(q->n_ref > 0); - q->n_ref++; - return q; -} - -DnsQuestion *dns_question_unref(DnsQuestion *q) { - if (!q) - return NULL; - - assert(q->n_ref > 0); - - if (q->n_ref == 1) { - unsigned i; - - for (i = 0; i < q->n_keys; i++) - dns_resource_key_unref(q->keys[i]); - free(q); - } else - q->n_ref--; - - return NULL; -} - -int dns_question_add(DnsQuestion *q, DnsResourceKey *key) { - unsigned i; - int r; - - assert(key); - - if (!q) - return -ENOSPC; - - for (i = 0; i < q->n_keys; i++) { - r = dns_resource_key_equal(q->keys[i], key); - if (r < 0) - return r; - if (r > 0) - return 0; - } - - if (q->n_keys >= q->n_allocated) - return -ENOSPC; - - q->keys[q->n_keys++] = dns_resource_key_ref(key); - return 0; -} - -int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) { - unsigned i; - int r; - - assert(rr); - - if (!q) - return 0; - - for (i = 0; i < q->n_keys; i++) { - r = dns_resource_key_match_rr(q->keys[i], rr, search_domain); - if (r != 0) - return r; - } - - return 0; -} - -int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) { - unsigned i; - int r; - - assert(rr); - - if (!q) - return 0; - - if (!IN_SET(rr->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)) - return 0; - - for (i = 0; i < q->n_keys; i++) { - /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */ - if (!dns_type_may_redirect(q->keys[i]->type)) - return 0; - - r = dns_resource_key_match_cname_or_dname(q->keys[i], rr->key, search_domain); - if (r != 0) - return r; - } - - return 0; -} - -int dns_question_is_valid_for_query(DnsQuestion *q) { - const char *name; - unsigned i; - int r; - - if (!q) - return 0; - - if (q->n_keys <= 0) - return 0; - - if (q->n_keys > 65535) - return 0; - - name = dns_resource_key_name(q->keys[0]); - if (!name) - return 0; - - /* Check that all keys in this question bear the same name */ - for (i = 0; i < q->n_keys; i++) { - assert(q->keys[i]); - - if (i > 0) { - r = dns_name_equal(dns_resource_key_name(q->keys[i]), name); - if (r <= 0) - return r; - } - - if (!dns_type_is_valid_query(q->keys[i]->type)) - return 0; - } - - return 1; -} - -int dns_question_contains(DnsQuestion *a, const DnsResourceKey *k) { - unsigned j; - int r; - - assert(k); - - if (!a) - return 0; - - for (j = 0; j < a->n_keys; j++) { - r = dns_resource_key_equal(a->keys[j], k); - if (r != 0) - return r; - } - - return 0; -} - -int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b) { - unsigned j; - int r; - - if (a == b) - return 1; - - if (!a) - return !b || b->n_keys == 0; - if (!b) - return a->n_keys == 0; - - /* Checks if all keys in a are also contained b, and vice versa */ - - for (j = 0; j < a->n_keys; j++) { - r = dns_question_contains(b, a->keys[j]); - if (r <= 0) - return r; - } - - for (j = 0; j < b->n_keys; j++) { - r = dns_question_contains(a, b->keys[j]); - if (r <= 0) - return r; - } - - return 1; -} - -int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret) { - _cleanup_(dns_question_unrefp) DnsQuestion *n = NULL; - DnsResourceKey *key; - bool same = true; - int r; - - assert(cname); - assert(ret); - assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)); - - if (dns_question_size(q) <= 0) { - *ret = NULL; - return 0; - } - - DNS_QUESTION_FOREACH(key, q) { - _cleanup_free_ char *destination = NULL; - const char *d; - - if (cname->key->type == DNS_TYPE_CNAME) - d = cname->cname.name; - else { - r = dns_name_change_suffix(dns_resource_key_name(key), dns_resource_key_name(cname->key), cname->dname.name, &destination); - if (r < 0) - return r; - if (r == 0) - continue; - - d = destination; - } - - r = dns_name_equal(dns_resource_key_name(key), d); - if (r < 0) - return r; - - if (r == 0) { - same = false; - break; - } - } - - /* Fully the same, indicate we didn't do a thing */ - if (same) { - *ret = NULL; - return 0; - } - - n = dns_question_new(q->n_keys); - if (!n) - return -ENOMEM; - - /* Create a new question, and patch in the new name */ - DNS_QUESTION_FOREACH(key, q) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; - - k = dns_resource_key_new_redirect(key, cname); - if (!k) - return -ENOMEM; - - r = dns_question_add(n, k); - if (r < 0) - return r; - } - - *ret = n; - n = NULL; - - return 1; -} - -const char *dns_question_first_name(DnsQuestion *q) { - - if (!q) - return NULL; - - if (q->n_keys < 1) - return NULL; - - return dns_resource_key_name(q->keys[0]); -} - -int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bool convert_idna) { - _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; - _cleanup_free_ char *buf = NULL; - int r; - - assert(ret); - assert(name); - - if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) - return -EAFNOSUPPORT; - - if (convert_idna) { - r = dns_name_apply_idna(name, &buf); - if (r < 0) - return r; - - name = buf; - } - - q = dns_question_new(family == AF_UNSPEC ? 2 : 1); - if (!q) - return -ENOMEM; - - if (family != AF_INET6) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - - key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, name); - if (!key) - return -ENOMEM; - - r = dns_question_add(q, key); - if (r < 0) - return r; - } - - if (family != AF_INET) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - - key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, name); - if (!key) - return -ENOMEM; - - r = dns_question_add(q, key); - if (r < 0) - return r; - } - - *ret = q; - q = NULL; - - return 0; -} - -int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; - _cleanup_free_ char *reverse = NULL; - int r; - - assert(ret); - assert(a); - - if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) - return -EAFNOSUPPORT; - - r = dns_name_reverse(family, a, &reverse); - if (r < 0) - return r; - - q = dns_question_new(1); - if (!q) - return -ENOMEM; - - key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, reverse); - if (!key) - return -ENOMEM; - - reverse = NULL; - - r = dns_question_add(q, key); - if (r < 0) - return r; - - *ret = q; - q = NULL; - - return 0; -} - -int dns_question_new_service( - DnsQuestion **ret, - const char *service, - const char *type, - const char *domain, - bool with_txt, - bool convert_idna) { - - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; - _cleanup_free_ char *buf = NULL, *joined = NULL; - const char *name; - int r; - - assert(ret); - - /* We support three modes of invocation: - * - * 1. Only a domain is specified, in which case we assume a properly encoded SRV RR name, including service - * type and possibly a service name. If specified in this way we assume it's already IDNA converted if - * that's necessary. - * - * 2. Both service type and a domain specified, in which case a normal SRV RR is assumed, without a DNS-SD - * style prefix. In this case we'll IDNA convert the domain, if that's requested. - * - * 3. All three of service name, type and domain are specified, in which case a DNS-SD service is put - * together. The service name is never IDNA converted, and the domain is if requested. - * - * It's not supported to specify a service name without a type, or no domain name. - */ - - if (!domain) - return -EINVAL; - - if (type) { - if (convert_idna) { - r = dns_name_apply_idna(domain, &buf); - if (r < 0) - return r; - - domain = buf; - } - - r = dns_service_join(service, type, domain, &joined); - if (r < 0) - return r; - - name = joined; - } else { - if (service) - return -EINVAL; - - name = domain; - } - - q = dns_question_new(1 + with_txt); - if (!q) - return -ENOMEM; - - key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_SRV, name); - if (!key) - return -ENOMEM; - - r = dns_question_add(q, key); - if (r < 0) - return r; - - if (with_txt) { - dns_resource_key_unref(key); - key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_TXT, name); - if (!key) - return -ENOMEM; - - r = dns_question_add(q, key); - if (r < 0) - return r; - } - - *ret = q; - q = NULL; - - return 0; -} diff --git a/src/resolve/resolved-dns-question.h b/src/resolve/resolved-dns-question.h deleted file mode 100644 index a9a1863b1e..0000000000 --- a/src/resolve/resolved-dns-question.h +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -typedef struct DnsQuestion DnsQuestion; - -#include "macro.h" -#include "resolved-dns-rr.h" - -/* A simple array of resource keys */ - -struct DnsQuestion { - unsigned n_ref; - unsigned n_keys, n_allocated; - DnsResourceKey* keys[0]; -}; - -DnsQuestion *dns_question_new(unsigned n); -DnsQuestion *dns_question_ref(DnsQuestion *q); -DnsQuestion *dns_question_unref(DnsQuestion *q); - -int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bool convert_idna); -int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a); -int dns_question_new_service(DnsQuestion **ret, const char *service, const char *type, const char *domain, bool with_txt, bool convert_idna); - -int dns_question_add(DnsQuestion *q, DnsResourceKey *key); - -int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain); -int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char* search_domain); -int dns_question_is_valid_for_query(DnsQuestion *q); -int dns_question_contains(DnsQuestion *a, const DnsResourceKey *k); -int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b); - -int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret); - -const char *dns_question_first_name(DnsQuestion *q); - -static inline unsigned dns_question_size(DnsQuestion *q) { - return q ? q->n_keys : 0; -} - -static inline bool dns_question_isempty(DnsQuestion *q) { - return dns_question_size(q) <= 0; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuestion*, dns_question_unref); - -#define _DNS_QUESTION_FOREACH(u, key, q) \ - for (unsigned UNIQ_T(i, u) = ({ \ - (key) = ((q) && (q)->n_keys > 0) ? (q)->keys[0] : NULL; \ - 0; \ - }); \ - (q) && (UNIQ_T(i, u) < (q)->n_keys); \ - UNIQ_T(i, u)++, (key) = (UNIQ_T(i, u) < (q)->n_keys ? (q)->keys[UNIQ_T(i, u)] : NULL)) - -#define DNS_QUESTION_FOREACH(key, q) _DNS_QUESTION_FOREACH(UNIQ, key, q) diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c deleted file mode 100644 index 87e4abec6e..0000000000 --- a/src/resolve/resolved-dns-rr.c +++ /dev/null @@ -1,1835 +0,0 @@ -/*** - 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 <math.h> - -#include "alloc-util.h" -#include "dns-domain.h" -#include "dns-type.h" -#include "escape.h" -#include "hexdecoct.h" -#include "resolved-dns-dnssec.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-rr.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "terminal-util.h" - -DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name) { - DnsResourceKey *k; - size_t l; - - assert(name); - - l = strlen(name); - k = malloc0(sizeof(DnsResourceKey) + l + 1); - if (!k) - return NULL; - - k->n_ref = 1; - k->class = class; - k->type = type; - - strcpy((char*) k + sizeof(DnsResourceKey), name); - - return k; -} - -DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname) { - int r; - - assert(key); - assert(cname); - - assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)); - - if (cname->key->type == DNS_TYPE_CNAME) - return dns_resource_key_new(key->class, key->type, cname->cname.name); - else { - DnsResourceKey *k; - char *destination = NULL; - - r = dns_name_change_suffix(dns_resource_key_name(key), dns_resource_key_name(cname->key), cname->dname.name, &destination); - if (r < 0) - return NULL; - if (r == 0) - return dns_resource_key_ref((DnsResourceKey*) key); - - k = dns_resource_key_new_consume(key->class, key->type, destination); - if (!k) - return mfree(destination); - - return k; - } -} - -int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name) { - DnsResourceKey *new_key; - char *joined; - int r; - - assert(ret); - assert(key); - assert(name); - - if (dns_name_is_root(name)) { - *ret = dns_resource_key_ref(key); - return 0; - } - - r = dns_name_concat(dns_resource_key_name(key), name, &joined); - if (r < 0) - return r; - - new_key = dns_resource_key_new_consume(key->class, key->type, joined); - if (!new_key) { - free(joined); - return -ENOMEM; - } - - *ret = new_key; - return 0; -} - -DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name) { - DnsResourceKey *k; - - assert(name); - - k = new0(DnsResourceKey, 1); - if (!k) - return NULL; - - k->n_ref = 1; - k->class = class; - k->type = type; - k->_name = name; - - return k; -} - -DnsResourceKey* dns_resource_key_ref(DnsResourceKey *k) { - - if (!k) - return NULL; - - /* Static/const keys created with DNS_RESOURCE_KEY_CONST will - * set this to -1, they should not be reffed/unreffed */ - assert(k->n_ref != (unsigned) -1); - - assert(k->n_ref > 0); - k->n_ref++; - - return k; -} - -DnsResourceKey* dns_resource_key_unref(DnsResourceKey *k) { - if (!k) - return NULL; - - assert(k->n_ref != (unsigned) -1); - assert(k->n_ref > 0); - - if (k->n_ref == 1) { - free(k->_name); - free(k); - } else - k->n_ref--; - - return NULL; -} - -const char* dns_resource_key_name(const DnsResourceKey *key) { - const char *name; - - if (!key) - return NULL; - - if (key->_name) - name = key->_name; - else - name = (char*) key + sizeof(DnsResourceKey); - - if (dns_name_is_root(name)) - return "."; - else - return name; -} - -bool dns_resource_key_is_address(const DnsResourceKey *key) { - assert(key); - - /* Check if this is an A or AAAA resource key */ - - return key->class == DNS_CLASS_IN && IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_AAAA); -} - -int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) { - int r; - - if (a == b) - return 1; - - r = dns_name_equal(dns_resource_key_name(a), dns_resource_key_name(b)); - if (r <= 0) - return r; - - if (a->class != b->class) - return 0; - - if (a->type != b->type) - return 0; - - return 1; -} - -int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain) { - int r; - - assert(key); - assert(rr); - - if (key == rr->key) - return 1; - - /* Checks if an rr matches the specified key. If a search - * domain is specified, it will also be checked if the key - * with the search domain suffixed might match the RR. */ - - if (rr->key->class != key->class && key->class != DNS_CLASS_ANY) - return 0; - - if (rr->key->type != key->type && key->type != DNS_TYPE_ANY) - return 0; - - r = dns_name_equal(dns_resource_key_name(rr->key), dns_resource_key_name(key)); - if (r != 0) - return r; - - if (search_domain) { - _cleanup_free_ char *joined = NULL; - - r = dns_name_concat(dns_resource_key_name(key), search_domain, &joined); - if (r < 0) - return r; - - return dns_name_equal(dns_resource_key_name(rr->key), joined); - } - - return 0; -} - -int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain) { - int r; - - assert(key); - assert(cname); - - if (cname->class != key->class && key->class != DNS_CLASS_ANY) - return 0; - - if (cname->type == DNS_TYPE_CNAME) - r = dns_name_equal(dns_resource_key_name(key), dns_resource_key_name(cname)); - else if (cname->type == DNS_TYPE_DNAME) - r = dns_name_endswith(dns_resource_key_name(key), dns_resource_key_name(cname)); - else - return 0; - - if (r != 0) - return r; - - if (search_domain) { - _cleanup_free_ char *joined = NULL; - - r = dns_name_concat(dns_resource_key_name(key), search_domain, &joined); - if (r < 0) - return r; - - if (cname->type == DNS_TYPE_CNAME) - return dns_name_equal(joined, dns_resource_key_name(cname)); - else if (cname->type == DNS_TYPE_DNAME) - return dns_name_endswith(joined, dns_resource_key_name(cname)); - } - - return 0; -} - -int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *soa) { - assert(soa); - assert(key); - - /* Checks whether 'soa' is a SOA record for the specified key. */ - - if (soa->class != key->class) - return 0; - - if (soa->type != DNS_TYPE_SOA) - return 0; - - return dns_name_endswith(dns_resource_key_name(key), dns_resource_key_name(soa)); -} - -static void dns_resource_key_hash_func(const void *i, struct siphash *state) { - const DnsResourceKey *k = i; - - assert(k); - - dns_name_hash_func(dns_resource_key_name(k), state); - siphash24_compress(&k->class, sizeof(k->class), state); - siphash24_compress(&k->type, sizeof(k->type), state); -} - -static int dns_resource_key_compare_func(const void *a, const void *b) { - const DnsResourceKey *x = a, *y = b; - int ret; - - ret = dns_name_compare_func(dns_resource_key_name(x), dns_resource_key_name(y)); - if (ret != 0) - return ret; - - if (x->type < y->type) - return -1; - if (x->type > y->type) - return 1; - - if (x->class < y->class) - return -1; - if (x->class > y->class) - return 1; - - return 0; -} - -const struct hash_ops dns_resource_key_hash_ops = { - .hash = dns_resource_key_hash_func, - .compare = dns_resource_key_compare_func -}; - -char* dns_resource_key_to_string(const DnsResourceKey *key, char *buf, size_t buf_size) { - const char *c, *t; - char *ans = buf; - - /* If we cannot convert the CLASS/TYPE into a known string, - use the format recommended by RFC 3597, Section 5. */ - - c = dns_class_to_string(key->class); - t = dns_type_to_string(key->type); - - snprintf(buf, buf_size, "%s %s%s%.0u %s%s%.0u", - dns_resource_key_name(key), - c ?: "", c ? "" : "CLASS", c ? 0 : key->class, - t ?: "", t ? "" : "TYPE", t ? 0 : key->class); - - return ans; -} - -bool dns_resource_key_reduce(DnsResourceKey **a, DnsResourceKey **b) { - assert(a); - assert(b); - - /* Try to replace one RR key by another if they are identical, thus saving a bit of memory. Note that we do - * this only for RR keys, not for RRs themselves, as they carry a lot of additional metadata (where they come - * from, validity data, and suchlike), and cannot be replaced so easily by other RRs that have the same - * superficial data. */ - - if (!*a) - return false; - if (!*b) - return false; - - /* We refuse merging const keys */ - if ((*a)->n_ref == (unsigned) -1) - return false; - if ((*b)->n_ref == (unsigned) -1) - return false; - - /* Already the same? */ - if (*a == *b) - return true; - - /* Are they really identical? */ - if (dns_resource_key_equal(*a, *b) <= 0) - return false; - - /* Keep the one which already has more references. */ - if ((*a)->n_ref > (*b)->n_ref) { - dns_resource_key_unref(*b); - *b = dns_resource_key_ref(*a); - } else { - dns_resource_key_unref(*a); - *a = dns_resource_key_ref(*b); - } - - return true; -} - -DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key) { - DnsResourceRecord *rr; - - rr = new0(DnsResourceRecord, 1); - if (!rr) - return NULL; - - rr->n_ref = 1; - rr->key = dns_resource_key_ref(key); - rr->expiry = USEC_INFINITY; - rr->n_skip_labels_signer = rr->n_skip_labels_source = (unsigned) -1; - - return rr; -} - -DnsResourceRecord* dns_resource_record_new_full(uint16_t class, uint16_t type, const char *name) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - - key = dns_resource_key_new(class, type, name); - if (!key) - return NULL; - - return dns_resource_record_new(key); -} - -DnsResourceRecord* dns_resource_record_ref(DnsResourceRecord *rr) { - if (!rr) - return NULL; - - assert(rr->n_ref > 0); - rr->n_ref++; - - return rr; -} - -DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr) { - if (!rr) - return NULL; - - assert(rr->n_ref > 0); - - if (rr->n_ref > 1) { - rr->n_ref--; - return NULL; - } - - if (rr->key) { - switch(rr->key->type) { - - case DNS_TYPE_SRV: - free(rr->srv.name); - break; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - free(rr->ptr.name); - break; - - case DNS_TYPE_HINFO: - free(rr->hinfo.cpu); - free(rr->hinfo.os); - break; - - case DNS_TYPE_TXT: - case DNS_TYPE_SPF: - dns_txt_item_free_all(rr->txt.items); - break; - - case DNS_TYPE_SOA: - free(rr->soa.mname); - free(rr->soa.rname); - break; - - case DNS_TYPE_MX: - free(rr->mx.exchange); - break; - - case DNS_TYPE_DS: - free(rr->ds.digest); - break; - - case DNS_TYPE_SSHFP: - free(rr->sshfp.fingerprint); - break; - - case DNS_TYPE_DNSKEY: - free(rr->dnskey.key); - break; - - case DNS_TYPE_RRSIG: - free(rr->rrsig.signer); - free(rr->rrsig.signature); - break; - - case DNS_TYPE_NSEC: - free(rr->nsec.next_domain_name); - bitmap_free(rr->nsec.types); - break; - - case DNS_TYPE_NSEC3: - free(rr->nsec3.next_hashed_name); - free(rr->nsec3.salt); - bitmap_free(rr->nsec3.types); - break; - - case DNS_TYPE_LOC: - case DNS_TYPE_A: - case DNS_TYPE_AAAA: - break; - - case DNS_TYPE_TLSA: - free(rr->tlsa.data); - break; - - case DNS_TYPE_CAA: - free(rr->caa.tag); - free(rr->caa.value); - break; - - case DNS_TYPE_OPENPGPKEY: - default: - free(rr->generic.data); - } - - free(rr->wire_format); - dns_resource_key_unref(rr->key); - } - - free(rr->to_string); - return mfree(rr); -} - -int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *hostname) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_free_ char *ptr = NULL; - int r; - - assert(ret); - assert(address); - assert(hostname); - - r = dns_name_reverse(family, address, &ptr); - if (r < 0) - return r; - - key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, ptr); - if (!key) - return -ENOMEM; - - ptr = NULL; - - rr = dns_resource_record_new(key); - if (!rr) - return -ENOMEM; - - rr->ptr.name = strdup(hostname); - if (!rr->ptr.name) - return -ENOMEM; - - *ret = rr; - rr = NULL; - - return 0; -} - -int dns_resource_record_new_address(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name) { - DnsResourceRecord *rr; - - assert(ret); - assert(address); - assert(family); - - if (family == AF_INET) { - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, name); - if (!rr) - return -ENOMEM; - - rr->a.in_addr = address->in; - - } else if (family == AF_INET6) { - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_AAAA, name); - if (!rr) - return -ENOMEM; - - rr->aaaa.in6_addr = address->in6; - } else - return -EAFNOSUPPORT; - - *ret = rr; - - return 0; -} - -#define FIELD_EQUAL(a, b, field) \ - ((a).field ## _size == (b).field ## _size && \ - memcmp((a).field, (b).field, (a).field ## _size) == 0) - -int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecord *b) { - int r; - - assert(a); - assert(b); - - if (a == b) - return 1; - - r = dns_resource_key_equal(a->key, b->key); - if (r <= 0) - return r; - - if (a->unparseable != b->unparseable) - return 0; - - switch (a->unparseable ? _DNS_TYPE_INVALID : a->key->type) { - - case DNS_TYPE_SRV: - r = dns_name_equal(a->srv.name, b->srv.name); - if (r <= 0) - return r; - - return a->srv.priority == b->srv.priority && - a->srv.weight == b->srv.weight && - a->srv.port == b->srv.port; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - return dns_name_equal(a->ptr.name, b->ptr.name); - - case DNS_TYPE_HINFO: - return strcaseeq(a->hinfo.cpu, b->hinfo.cpu) && - strcaseeq(a->hinfo.os, b->hinfo.os); - - case DNS_TYPE_SPF: /* exactly the same as TXT */ - case DNS_TYPE_TXT: - return dns_txt_item_equal(a->txt.items, b->txt.items); - - case DNS_TYPE_A: - return memcmp(&a->a.in_addr, &b->a.in_addr, sizeof(struct in_addr)) == 0; - - case DNS_TYPE_AAAA: - return memcmp(&a->aaaa.in6_addr, &b->aaaa.in6_addr, sizeof(struct in6_addr)) == 0; - - case DNS_TYPE_SOA: - r = dns_name_equal(a->soa.mname, b->soa.mname); - if (r <= 0) - return r; - r = dns_name_equal(a->soa.rname, b->soa.rname); - if (r <= 0) - return r; - - return a->soa.serial == b->soa.serial && - a->soa.refresh == b->soa.refresh && - a->soa.retry == b->soa.retry && - a->soa.expire == b->soa.expire && - a->soa.minimum == b->soa.minimum; - - case DNS_TYPE_MX: - if (a->mx.priority != b->mx.priority) - return 0; - - return dns_name_equal(a->mx.exchange, b->mx.exchange); - - case DNS_TYPE_LOC: - assert(a->loc.version == b->loc.version); - - return a->loc.size == b->loc.size && - a->loc.horiz_pre == b->loc.horiz_pre && - a->loc.vert_pre == b->loc.vert_pre && - a->loc.latitude == b->loc.latitude && - a->loc.longitude == b->loc.longitude && - a->loc.altitude == b->loc.altitude; - - case DNS_TYPE_DS: - return a->ds.key_tag == b->ds.key_tag && - a->ds.algorithm == b->ds.algorithm && - a->ds.digest_type == b->ds.digest_type && - FIELD_EQUAL(a->ds, b->ds, digest); - - case DNS_TYPE_SSHFP: - return a->sshfp.algorithm == b->sshfp.algorithm && - a->sshfp.fptype == b->sshfp.fptype && - FIELD_EQUAL(a->sshfp, b->sshfp, fingerprint); - - case DNS_TYPE_DNSKEY: - return a->dnskey.flags == b->dnskey.flags && - a->dnskey.protocol == b->dnskey.protocol && - a->dnskey.algorithm == b->dnskey.algorithm && - FIELD_EQUAL(a->dnskey, b->dnskey, key); - - case DNS_TYPE_RRSIG: - /* do the fast comparisons first */ - return a->rrsig.type_covered == b->rrsig.type_covered && - a->rrsig.algorithm == b->rrsig.algorithm && - a->rrsig.labels == b->rrsig.labels && - a->rrsig.original_ttl == b->rrsig.original_ttl && - a->rrsig.expiration == b->rrsig.expiration && - a->rrsig.inception == b->rrsig.inception && - a->rrsig.key_tag == b->rrsig.key_tag && - FIELD_EQUAL(a->rrsig, b->rrsig, signature) && - dns_name_equal(a->rrsig.signer, b->rrsig.signer); - - case DNS_TYPE_NSEC: - return dns_name_equal(a->nsec.next_domain_name, b->nsec.next_domain_name) && - bitmap_equal(a->nsec.types, b->nsec.types); - - case DNS_TYPE_NSEC3: - return a->nsec3.algorithm == b->nsec3.algorithm && - a->nsec3.flags == b->nsec3.flags && - a->nsec3.iterations == b->nsec3.iterations && - FIELD_EQUAL(a->nsec3, b->nsec3, salt) && - FIELD_EQUAL(a->nsec3, b->nsec3, next_hashed_name) && - bitmap_equal(a->nsec3.types, b->nsec3.types); - - case DNS_TYPE_TLSA: - return a->tlsa.cert_usage == b->tlsa.cert_usage && - a->tlsa.selector == b->tlsa.selector && - a->tlsa.matching_type == b->tlsa.matching_type && - FIELD_EQUAL(a->tlsa, b->tlsa, data); - - case DNS_TYPE_CAA: - return a->caa.flags == b->caa.flags && - streq(a->caa.tag, b->caa.tag) && - FIELD_EQUAL(a->caa, b->caa, value); - - case DNS_TYPE_OPENPGPKEY: - default: - return FIELD_EQUAL(a->generic, b->generic, data); - } -} - -static char* format_location(uint32_t latitude, uint32_t longitude, uint32_t altitude, - uint8_t size, uint8_t horiz_pre, uint8_t vert_pre) { - char *s; - char NS = latitude >= 1U<<31 ? 'N' : 'S'; - char EW = longitude >= 1U<<31 ? 'E' : 'W'; - - int lat = latitude >= 1U<<31 ? (int) (latitude - (1U<<31)) : (int) ((1U<<31) - latitude); - int lon = longitude >= 1U<<31 ? (int) (longitude - (1U<<31)) : (int) ((1U<<31) - longitude); - double alt = altitude >= 10000000u ? altitude - 10000000u : -(double)(10000000u - altitude); - double siz = (size >> 4) * exp10((double) (size & 0xF)); - double hor = (horiz_pre >> 4) * exp10((double) (horiz_pre & 0xF)); - double ver = (vert_pre >> 4) * exp10((double) (vert_pre & 0xF)); - - if (asprintf(&s, "%d %d %.3f %c %d %d %.3f %c %.2fm %.2fm %.2fm %.2fm", - (lat / 60000 / 60), - (lat / 60000) % 60, - (lat % 60000) / 1000., - NS, - (lon / 60000 / 60), - (lon / 60000) % 60, - (lon % 60000) / 1000., - EW, - alt / 100., - siz / 100., - hor / 100., - ver / 100.) < 0) - return NULL; - - return s; -} - -static int format_timestamp_dns(char *buf, size_t l, time_t sec) { - struct tm tm; - - assert(buf); - assert(l > strlen("YYYYMMDDHHmmSS")); - - if (!gmtime_r(&sec, &tm)) - return -EINVAL; - - if (strftime(buf, l, "%Y%m%d%H%M%S", &tm) <= 0) - return -EINVAL; - - return 0; -} - -static char *format_types(Bitmap *types) { - _cleanup_strv_free_ char **strv = NULL; - _cleanup_free_ char *str = NULL; - Iterator i; - unsigned type; - int r; - - BITMAP_FOREACH(type, types, i) { - if (dns_type_to_string(type)) { - r = strv_extend(&strv, dns_type_to_string(type)); - if (r < 0) - return NULL; - } else { - char *t; - - r = asprintf(&t, "TYPE%u", type); - if (r < 0) - return NULL; - - r = strv_consume(&strv, t); - if (r < 0) - return NULL; - } - } - - str = strv_join(strv, " "); - if (!str) - return NULL; - - return strjoin("( ", str, " )", NULL); -} - -static char *format_txt(DnsTxtItem *first) { - DnsTxtItem *i; - size_t c = 1; - char *p, *s; - - LIST_FOREACH(items, i, first) - c += i->length * 4 + 3; - - p = s = new(char, c); - if (!s) - return NULL; - - LIST_FOREACH(items, i, first) { - size_t j; - - if (i != first) - *(p++) = ' '; - - *(p++) = '"'; - - for (j = 0; j < i->length; j++) { - if (i->data[j] < ' ' || i->data[j] == '"' || i->data[j] >= 127) { - *(p++) = '\\'; - *(p++) = '0' + (i->data[j] / 100); - *(p++) = '0' + ((i->data[j] / 10) % 10); - *(p++) = '0' + (i->data[j] % 10); - } else - *(p++) = i->data[j]; - } - - *(p++) = '"'; - } - - *p = 0; - return s; -} - -const char *dns_resource_record_to_string(DnsResourceRecord *rr) { - _cleanup_free_ char *t = NULL; - char *s, k[DNS_RESOURCE_KEY_STRING_MAX]; - int r; - - assert(rr); - - if (rr->to_string) - return rr->to_string; - - dns_resource_key_to_string(rr->key, k, sizeof(k)); - - switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { - - case DNS_TYPE_SRV: - r = asprintf(&s, "%s %u %u %u %s", - k, - rr->srv.priority, - rr->srv.weight, - rr->srv.port, - strna(rr->srv.name)); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - s = strjoin(k, " ", rr->ptr.name, NULL); - if (!s) - return NULL; - - break; - - case DNS_TYPE_HINFO: - s = strjoin(k, " ", rr->hinfo.cpu, " ", rr->hinfo.os, NULL); - if (!s) - return NULL; - break; - - case DNS_TYPE_SPF: /* exactly the same as TXT */ - case DNS_TYPE_TXT: - t = format_txt(rr->txt.items); - if (!t) - return NULL; - - s = strjoin(k, " ", t, NULL); - if (!s) - return NULL; - break; - - case DNS_TYPE_A: { - _cleanup_free_ char *x = NULL; - - r = in_addr_to_string(AF_INET, (const union in_addr_union*) &rr->a.in_addr, &x); - if (r < 0) - return NULL; - - s = strjoin(k, " ", x, NULL); - if (!s) - return NULL; - break; - } - - case DNS_TYPE_AAAA: - r = in_addr_to_string(AF_INET6, (const union in_addr_union*) &rr->aaaa.in6_addr, &t); - if (r < 0) - return NULL; - - s = strjoin(k, " ", t, NULL); - if (!s) - return NULL; - break; - - case DNS_TYPE_SOA: - r = asprintf(&s, "%s %s %s %u %u %u %u %u", - k, - strna(rr->soa.mname), - strna(rr->soa.rname), - rr->soa.serial, - rr->soa.refresh, - rr->soa.retry, - rr->soa.expire, - rr->soa.minimum); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_MX: - r = asprintf(&s, "%s %u %s", - k, - rr->mx.priority, - rr->mx.exchange); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_LOC: - assert(rr->loc.version == 0); - - t = format_location(rr->loc.latitude, - rr->loc.longitude, - rr->loc.altitude, - rr->loc.size, - rr->loc.horiz_pre, - rr->loc.vert_pre); - if (!t) - return NULL; - - s = strjoin(k, " ", t, NULL); - if (!s) - return NULL; - break; - - case DNS_TYPE_DS: - t = hexmem(rr->ds.digest, rr->ds.digest_size); - if (!t) - return NULL; - - r = asprintf(&s, "%s %u %u %u %s", - k, - rr->ds.key_tag, - rr->ds.algorithm, - rr->ds.digest_type, - t); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_SSHFP: - t = hexmem(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size); - if (!t) - return NULL; - - r = asprintf(&s, "%s %u %u %s", - k, - rr->sshfp.algorithm, - rr->sshfp.fptype, - t); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_DNSKEY: { - _cleanup_free_ char *alg = NULL; - char *ss; - int n; - uint16_t key_tag; - - key_tag = dnssec_keytag(rr, true); - - r = dnssec_algorithm_to_string_alloc(rr->dnskey.algorithm, &alg); - if (r < 0) - return NULL; - - r = asprintf(&s, "%s %u %u %s %n", - k, - rr->dnskey.flags, - rr->dnskey.protocol, - alg, - &n); - if (r < 0) - return NULL; - - r = base64_append(&s, n, - rr->dnskey.key, rr->dnskey.key_size, - 8, columns()); - if (r < 0) - return NULL; - - r = asprintf(&ss, "%s\n" - " -- Flags:%s%s%s\n" - " -- Key tag: %u", - s, - rr->dnskey.flags & DNSKEY_FLAG_SEP ? " SEP" : "", - rr->dnskey.flags & DNSKEY_FLAG_REVOKE ? " REVOKE" : "", - rr->dnskey.flags & DNSKEY_FLAG_ZONE_KEY ? " ZONE_KEY" : "", - key_tag); - if (r < 0) - return NULL; - free(s); - s = ss; - - break; - } - - case DNS_TYPE_RRSIG: { - _cleanup_free_ char *alg = NULL; - char expiration[strlen("YYYYMMDDHHmmSS") + 1], inception[strlen("YYYYMMDDHHmmSS") + 1]; - const char *type; - int n; - - type = dns_type_to_string(rr->rrsig.type_covered); - - r = dnssec_algorithm_to_string_alloc(rr->rrsig.algorithm, &alg); - if (r < 0) - return NULL; - - r = format_timestamp_dns(expiration, sizeof(expiration), rr->rrsig.expiration); - if (r < 0) - return NULL; - - r = format_timestamp_dns(inception, sizeof(inception), rr->rrsig.inception); - if (r < 0) - return NULL; - - /* TYPE?? follows - * http://tools.ietf.org/html/rfc3597#section-5 */ - - r = asprintf(&s, "%s %s%.*u %s %u %u %s %s %u %s %n", - k, - type ?: "TYPE", - type ? 0 : 1, type ? 0u : (unsigned) rr->rrsig.type_covered, - alg, - rr->rrsig.labels, - rr->rrsig.original_ttl, - expiration, - inception, - rr->rrsig.key_tag, - rr->rrsig.signer, - &n); - if (r < 0) - return NULL; - - r = base64_append(&s, n, - rr->rrsig.signature, rr->rrsig.signature_size, - 8, columns()); - if (r < 0) - return NULL; - - break; - } - - case DNS_TYPE_NSEC: - t = format_types(rr->nsec.types); - if (!t) - return NULL; - - r = asprintf(&s, "%s %s %s", - k, - rr->nsec.next_domain_name, - t); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_NSEC3: { - _cleanup_free_ char *salt = NULL, *hash = NULL; - - if (rr->nsec3.salt_size > 0) { - salt = hexmem(rr->nsec3.salt, rr->nsec3.salt_size); - if (!salt) - return NULL; - } - - hash = base32hexmem(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, false); - if (!hash) - return NULL; - - t = format_types(rr->nsec3.types); - if (!t) - return NULL; - - r = asprintf(&s, "%s %"PRIu8" %"PRIu8" %"PRIu16" %s %s %s", - k, - rr->nsec3.algorithm, - rr->nsec3.flags, - rr->nsec3.iterations, - rr->nsec3.salt_size > 0 ? salt : "-", - hash, - t); - if (r < 0) - return NULL; - - break; - } - - case DNS_TYPE_TLSA: { - const char *cert_usage, *selector, *matching_type; - - cert_usage = tlsa_cert_usage_to_string(rr->tlsa.cert_usage); - selector = tlsa_selector_to_string(rr->tlsa.selector); - matching_type = tlsa_matching_type_to_string(rr->tlsa.matching_type); - - t = hexmem(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size); - if (!t) - return NULL; - - r = asprintf(&s, - "%s %u %u %u %s\n" - " -- Cert. usage: %s\n" - " -- Selector: %s\n" - " -- Matching type: %s", - k, - rr->tlsa.cert_usage, - rr->tlsa.selector, - rr->tlsa.matching_type, - t, - cert_usage, - selector, - matching_type); - if (r < 0) - return NULL; - - break; - } - - case DNS_TYPE_CAA: { - _cleanup_free_ char *value; - - value = octescape(rr->caa.value, rr->caa.value_size); - if (!value) - return NULL; - - r = asprintf(&s, "%s %u %s \"%s\"%s%s%s%.0u", - k, - rr->caa.flags, - rr->caa.tag, - value, - rr->caa.flags ? "\n -- Flags:" : "", - rr->caa.flags & CAA_FLAG_CRITICAL ? " critical" : "", - rr->caa.flags & ~CAA_FLAG_CRITICAL ? " " : "", - rr->caa.flags & ~CAA_FLAG_CRITICAL); - if (r < 0) - return NULL; - - break; - } - - case DNS_TYPE_OPENPGPKEY: { - int n; - - r = asprintf(&s, "%s %n", - k, - &n); - if (r < 0) - return NULL; - - r = base64_append(&s, n, - rr->generic.data, rr->generic.data_size, - 8, columns()); - if (r < 0) - return NULL; - break; - } - - default: - t = hexmem(rr->generic.data, rr->generic.data_size); - if (!t) - return NULL; - - /* Format as documented in RFC 3597, Section 5 */ - r = asprintf(&s, "%s \\# %zu %s", k, rr->generic.data_size, t); - if (r < 0) - return NULL; - break; - } - - rr->to_string = s; - return s; -} - -ssize_t dns_resource_record_payload(DnsResourceRecord *rr, void **out) { - assert(rr); - assert(out); - - switch(rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { - case DNS_TYPE_SRV: - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - case DNS_TYPE_HINFO: - case DNS_TYPE_SPF: - case DNS_TYPE_TXT: - case DNS_TYPE_A: - case DNS_TYPE_AAAA: - case DNS_TYPE_SOA: - case DNS_TYPE_MX: - case DNS_TYPE_LOC: - case DNS_TYPE_DS: - case DNS_TYPE_DNSKEY: - case DNS_TYPE_RRSIG: - case DNS_TYPE_NSEC: - case DNS_TYPE_NSEC3: - return -EINVAL; - - case DNS_TYPE_SSHFP: - *out = rr->sshfp.fingerprint; - return rr->sshfp.fingerprint_size; - - case DNS_TYPE_TLSA: - *out = rr->tlsa.data; - return rr->tlsa.data_size; - - - case DNS_TYPE_OPENPGPKEY: - default: - *out = rr->generic.data; - return rr->generic.data_size; - } -} - -int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) { - - DnsPacket packet = { - .n_ref = 1, - .protocol = DNS_PROTOCOL_DNS, - .on_stack = true, - .refuse_compression = true, - .canonical_form = canonical, - }; - - size_t start, rds; - int r; - - assert(rr); - - /* Generates the RR in wire-format, optionally in the - * canonical form as discussed in the DNSSEC RFC 4034, Section - * 6.2. We allocate a throw-away DnsPacket object on the stack - * here, because we need some book-keeping for memory - * management, and can reuse the DnsPacket serializer, that - * can generate the canonical form, too, but also knows label - * compression and suchlike. */ - - if (rr->wire_format && rr->wire_format_canonical == canonical) - return 0; - - r = dns_packet_append_rr(&packet, rr, &start, &rds); - if (r < 0) - return r; - - assert(start == 0); - assert(packet._data); - - free(rr->wire_format); - rr->wire_format = packet._data; - rr->wire_format_size = packet.size; - rr->wire_format_rdata_offset = rds; - rr->wire_format_canonical = canonical; - - packet._data = NULL; - dns_packet_unref(&packet); - - return 0; -} - -int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret) { - const char *n; - int r; - - assert(rr); - assert(ret); - - /* Returns the RRset's signer, if it is known. */ - - if (rr->n_skip_labels_signer == (unsigned) -1) - return -ENODATA; - - n = dns_resource_key_name(rr->key); - r = dns_name_skip(n, rr->n_skip_labels_signer, &n); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - *ret = n; - return 0; -} - -int dns_resource_record_source(DnsResourceRecord *rr, const char **ret) { - const char *n; - int r; - - assert(rr); - assert(ret); - - /* Returns the RRset's synthesizing source, if it is known. */ - - if (rr->n_skip_labels_source == (unsigned) -1) - return -ENODATA; - - n = dns_resource_key_name(rr->key); - r = dns_name_skip(n, rr->n_skip_labels_source, &n); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - *ret = n; - return 0; -} - -int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone) { - const char *signer; - int r; - - assert(rr); - - r = dns_resource_record_signer(rr, &signer); - if (r < 0) - return r; - - return dns_name_equal(zone, signer); -} - -int dns_resource_record_is_synthetic(DnsResourceRecord *rr) { - int r; - - assert(rr); - - /* Returns > 0 if the RR is generated from a wildcard, and is not the asterisk name itself */ - - if (rr->n_skip_labels_source == (unsigned) -1) - return -ENODATA; - - if (rr->n_skip_labels_source == 0) - return 0; - - if (rr->n_skip_labels_source > 1) - return 1; - - r = dns_name_startswith(dns_resource_key_name(rr->key), "*"); - if (r < 0) - return r; - - return !r; -} - -void dns_resource_record_hash_func(const void *i, struct siphash *state) { - const DnsResourceRecord *rr = i; - - assert(rr); - - dns_resource_key_hash_func(rr->key, state); - - switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { - - case DNS_TYPE_SRV: - siphash24_compress(&rr->srv.priority, sizeof(rr->srv.priority), state); - siphash24_compress(&rr->srv.weight, sizeof(rr->srv.weight), state); - siphash24_compress(&rr->srv.port, sizeof(rr->srv.port), state); - dns_name_hash_func(rr->srv.name, state); - break; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - dns_name_hash_func(rr->ptr.name, state); - break; - - case DNS_TYPE_HINFO: - string_hash_func(rr->hinfo.cpu, state); - string_hash_func(rr->hinfo.os, state); - break; - - case DNS_TYPE_TXT: - case DNS_TYPE_SPF: { - DnsTxtItem *j; - - LIST_FOREACH(items, j, rr->txt.items) { - siphash24_compress(j->data, j->length, state); - - /* Add an extra NUL byte, so that "a" followed by "b" doesn't result in the same hash as "ab" - * followed by "". */ - siphash24_compress_byte(0, state); - } - break; - } - - case DNS_TYPE_A: - siphash24_compress(&rr->a.in_addr, sizeof(rr->a.in_addr), state); - break; - - case DNS_TYPE_AAAA: - siphash24_compress(&rr->aaaa.in6_addr, sizeof(rr->aaaa.in6_addr), state); - break; - - case DNS_TYPE_SOA: - dns_name_hash_func(rr->soa.mname, state); - dns_name_hash_func(rr->soa.rname, state); - siphash24_compress(&rr->soa.serial, sizeof(rr->soa.serial), state); - siphash24_compress(&rr->soa.refresh, sizeof(rr->soa.refresh), state); - siphash24_compress(&rr->soa.retry, sizeof(rr->soa.retry), state); - siphash24_compress(&rr->soa.expire, sizeof(rr->soa.expire), state); - siphash24_compress(&rr->soa.minimum, sizeof(rr->soa.minimum), state); - break; - - case DNS_TYPE_MX: - siphash24_compress(&rr->mx.priority, sizeof(rr->mx.priority), state); - dns_name_hash_func(rr->mx.exchange, state); - break; - - case DNS_TYPE_LOC: - siphash24_compress(&rr->loc.version, sizeof(rr->loc.version), state); - siphash24_compress(&rr->loc.size, sizeof(rr->loc.size), state); - siphash24_compress(&rr->loc.horiz_pre, sizeof(rr->loc.horiz_pre), state); - siphash24_compress(&rr->loc.vert_pre, sizeof(rr->loc.vert_pre), state); - siphash24_compress(&rr->loc.latitude, sizeof(rr->loc.latitude), state); - siphash24_compress(&rr->loc.longitude, sizeof(rr->loc.longitude), state); - siphash24_compress(&rr->loc.altitude, sizeof(rr->loc.altitude), state); - break; - - case DNS_TYPE_SSHFP: - siphash24_compress(&rr->sshfp.algorithm, sizeof(rr->sshfp.algorithm), state); - siphash24_compress(&rr->sshfp.fptype, sizeof(rr->sshfp.fptype), state); - siphash24_compress(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, state); - break; - - case DNS_TYPE_DNSKEY: - siphash24_compress(&rr->dnskey.flags, sizeof(rr->dnskey.flags), state); - siphash24_compress(&rr->dnskey.protocol, sizeof(rr->dnskey.protocol), state); - siphash24_compress(&rr->dnskey.algorithm, sizeof(rr->dnskey.algorithm), state); - siphash24_compress(rr->dnskey.key, rr->dnskey.key_size, state); - break; - - case DNS_TYPE_RRSIG: - siphash24_compress(&rr->rrsig.type_covered, sizeof(rr->rrsig.type_covered), state); - siphash24_compress(&rr->rrsig.algorithm, sizeof(rr->rrsig.algorithm), state); - siphash24_compress(&rr->rrsig.labels, sizeof(rr->rrsig.labels), state); - siphash24_compress(&rr->rrsig.original_ttl, sizeof(rr->rrsig.original_ttl), state); - siphash24_compress(&rr->rrsig.expiration, sizeof(rr->rrsig.expiration), state); - siphash24_compress(&rr->rrsig.inception, sizeof(rr->rrsig.inception), state); - siphash24_compress(&rr->rrsig.key_tag, sizeof(rr->rrsig.key_tag), state); - dns_name_hash_func(rr->rrsig.signer, state); - siphash24_compress(rr->rrsig.signature, rr->rrsig.signature_size, state); - break; - - case DNS_TYPE_NSEC: - dns_name_hash_func(rr->nsec.next_domain_name, state); - /* FIXME: we leave out the type bitmap here. Hash - * would be better if we'd take it into account - * too. */ - break; - - case DNS_TYPE_DS: - siphash24_compress(&rr->ds.key_tag, sizeof(rr->ds.key_tag), state); - siphash24_compress(&rr->ds.algorithm, sizeof(rr->ds.algorithm), state); - siphash24_compress(&rr->ds.digest_type, sizeof(rr->ds.digest_type), state); - siphash24_compress(rr->ds.digest, rr->ds.digest_size, state); - break; - - case DNS_TYPE_NSEC3: - siphash24_compress(&rr->nsec3.algorithm, sizeof(rr->nsec3.algorithm), state); - siphash24_compress(&rr->nsec3.flags, sizeof(rr->nsec3.flags), state); - siphash24_compress(&rr->nsec3.iterations, sizeof(rr->nsec3.iterations), state); - siphash24_compress(rr->nsec3.salt, rr->nsec3.salt_size, state); - siphash24_compress(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, state); - /* FIXME: We leave the bitmaps out */ - break; - - case DNS_TYPE_TLSA: - siphash24_compress(&rr->tlsa.cert_usage, sizeof(rr->tlsa.cert_usage), state); - siphash24_compress(&rr->tlsa.selector, sizeof(rr->tlsa.selector), state); - siphash24_compress(&rr->tlsa.matching_type, sizeof(rr->tlsa.matching_type), state); - siphash24_compress(rr->tlsa.data, rr->tlsa.data_size, state); - break; - - case DNS_TYPE_CAA: - siphash24_compress(&rr->caa.flags, sizeof(rr->caa.flags), state); - string_hash_func(rr->caa.tag, state); - siphash24_compress(rr->caa.value, rr->caa.value_size, state); - break; - - case DNS_TYPE_OPENPGPKEY: - default: - siphash24_compress(rr->generic.data, rr->generic.data_size, state); - break; - } -} - -static int dns_resource_record_compare_func(const void *a, const void *b) { - const DnsResourceRecord *x = a, *y = b; - int ret; - - ret = dns_resource_key_compare_func(x->key, y->key); - if (ret != 0) - return ret; - - if (dns_resource_record_equal(x, y)) - return 0; - - /* This is a bit dirty, we don't implement proper ordering, but - * the hashtable doesn't need ordering anyway, hence we don't - * care. */ - return x < y ? -1 : 1; -} - -const struct hash_ops dns_resource_record_hash_ops = { - .hash = dns_resource_record_hash_func, - .compare = dns_resource_record_compare_func, -}; - -DnsResourceRecord *dns_resource_record_copy(DnsResourceRecord *rr) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *copy = NULL; - DnsResourceRecord *t; - - assert(rr); - - copy = dns_resource_record_new(rr->key); - if (!copy) - return NULL; - - copy->ttl = rr->ttl; - copy->expiry = rr->expiry; - copy->n_skip_labels_signer = rr->n_skip_labels_signer; - copy->n_skip_labels_source = rr->n_skip_labels_source; - copy->unparseable = rr->unparseable; - - switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { - - case DNS_TYPE_SRV: - copy->srv.priority = rr->srv.priority; - copy->srv.weight = rr->srv.weight; - copy->srv.port = rr->srv.port; - copy->srv.name = strdup(rr->srv.name); - if (!copy->srv.name) - return NULL; - break; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - copy->ptr.name = strdup(rr->ptr.name); - if (!copy->ptr.name) - return NULL; - break; - - case DNS_TYPE_HINFO: - copy->hinfo.cpu = strdup(rr->hinfo.cpu); - if (!copy->hinfo.cpu) - return NULL; - - copy->hinfo.os = strdup(rr->hinfo.os); - if(!copy->hinfo.os) - return NULL; - break; - - case DNS_TYPE_TXT: - case DNS_TYPE_SPF: - copy->txt.items = dns_txt_item_copy(rr->txt.items); - if (!copy->txt.items) - return NULL; - break; - - case DNS_TYPE_A: - copy->a = rr->a; - break; - - case DNS_TYPE_AAAA: - copy->aaaa = rr->aaaa; - break; - - case DNS_TYPE_SOA: - copy->soa.mname = strdup(rr->soa.mname); - if (!copy->soa.mname) - return NULL; - copy->soa.rname = strdup(rr->soa.rname); - if (!copy->soa.rname) - return NULL; - copy->soa.serial = rr->soa.serial; - copy->soa.refresh = rr->soa.refresh; - copy->soa.retry = rr->soa.retry; - copy->soa.expire = rr->soa.expire; - copy->soa.minimum = rr->soa.minimum; - break; - - case DNS_TYPE_MX: - copy->mx.priority = rr->mx.priority; - copy->mx.exchange = strdup(rr->mx.exchange); - if (!copy->mx.exchange) - return NULL; - break; - - case DNS_TYPE_LOC: - copy->loc = rr->loc; - break; - - case DNS_TYPE_SSHFP: - copy->sshfp.algorithm = rr->sshfp.algorithm; - copy->sshfp.fptype = rr->sshfp.fptype; - copy->sshfp.fingerprint = memdup(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size); - if (!copy->sshfp.fingerprint) - return NULL; - copy->sshfp.fingerprint_size = rr->sshfp.fingerprint_size; - break; - - case DNS_TYPE_DNSKEY: - copy->dnskey.flags = rr->dnskey.flags; - copy->dnskey.protocol = rr->dnskey.protocol; - copy->dnskey.algorithm = rr->dnskey.algorithm; - copy->dnskey.key = memdup(rr->dnskey.key, rr->dnskey.key_size); - if (!copy->dnskey.key) - return NULL; - copy->dnskey.key_size = rr->dnskey.key_size; - break; - - case DNS_TYPE_RRSIG: - copy->rrsig.type_covered = rr->rrsig.type_covered; - copy->rrsig.algorithm = rr->rrsig.algorithm; - copy->rrsig.labels = rr->rrsig.labels; - copy->rrsig.original_ttl = rr->rrsig.original_ttl; - copy->rrsig.expiration = rr->rrsig.expiration; - copy->rrsig.inception = rr->rrsig.inception; - copy->rrsig.key_tag = rr->rrsig.key_tag; - copy->rrsig.signer = strdup(rr->rrsig.signer); - if (!copy->rrsig.signer) - return NULL; - copy->rrsig.signature = memdup(rr->rrsig.signature, rr->rrsig.signature_size); - if (!copy->rrsig.signature) - return NULL; - copy->rrsig.signature_size = rr->rrsig.signature_size; - break; - - case DNS_TYPE_NSEC: - copy->nsec.next_domain_name = strdup(rr->nsec.next_domain_name); - if (!copy->nsec.next_domain_name) - return NULL; - copy->nsec.types = bitmap_copy(rr->nsec.types); - if (!copy->nsec.types) - return NULL; - break; - - case DNS_TYPE_DS: - copy->ds.key_tag = rr->ds.key_tag; - copy->ds.algorithm = rr->ds.algorithm; - copy->ds.digest_type = rr->ds.digest_type; - copy->ds.digest = memdup(rr->ds.digest, rr->ds.digest_size); - if (!copy->ds.digest) - return NULL; - copy->ds.digest_size = rr->ds.digest_size; - break; - - case DNS_TYPE_NSEC3: - copy->nsec3.algorithm = rr->nsec3.algorithm; - copy->nsec3.flags = rr->nsec3.flags; - copy->nsec3.iterations = rr->nsec3.iterations; - copy->nsec3.salt = memdup(rr->nsec3.salt, rr->nsec3.salt_size); - if (!copy->nsec3.salt) - return NULL; - copy->nsec3.salt_size = rr->nsec3.salt_size; - copy->nsec3.next_hashed_name = memdup(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size); - if (!copy->nsec3.next_hashed_name_size) - return NULL; - copy->nsec3.next_hashed_name_size = rr->nsec3.next_hashed_name_size; - copy->nsec3.types = bitmap_copy(rr->nsec3.types); - if (!copy->nsec3.types) - return NULL; - break; - - case DNS_TYPE_TLSA: - copy->tlsa.cert_usage = rr->tlsa.cert_usage; - copy->tlsa.selector = rr->tlsa.selector; - copy->tlsa.matching_type = rr->tlsa.matching_type; - copy->tlsa.data = memdup(rr->tlsa.data, rr->tlsa.data_size); - if (!copy->tlsa.data) - return NULL; - copy->tlsa.data_size = rr->tlsa.data_size; - break; - - case DNS_TYPE_CAA: - copy->caa.flags = rr->caa.flags; - copy->caa.tag = strdup(rr->caa.tag); - if (!copy->caa.tag) - return NULL; - copy->caa.value = memdup(rr->caa.value, rr->caa.value_size); - if (!copy->caa.value) - return NULL; - copy->caa.value_size = rr->caa.value_size; - break; - - case DNS_TYPE_OPT: - default: - copy->generic.data = memdup(rr->generic.data, rr->generic.data_size); - if (!copy->generic.data) - return NULL; - copy->generic.data_size = rr->generic.data_size; - break; - } - - t = copy; - copy = NULL; - - return t; -} - -int dns_resource_record_clamp_ttl(DnsResourceRecord **rr, uint32_t max_ttl) { - DnsResourceRecord *old_rr, *new_rr; - uint32_t new_ttl; - - assert(rr); - old_rr = *rr; - - if (old_rr->key->type == DNS_TYPE_OPT) - return -EINVAL; - - new_ttl = MIN(old_rr->ttl, max_ttl); - if (new_ttl == old_rr->ttl) - return 0; - - if (old_rr->n_ref == 1) { - /* Patch in place */ - old_rr->ttl = new_ttl; - return 1; - } - - new_rr = dns_resource_record_copy(old_rr); - if (!new_rr) - return -ENOMEM; - - new_rr->ttl = new_ttl; - - dns_resource_record_unref(*rr); - *rr = new_rr; - - return 1; -} - -DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i) { - DnsTxtItem *n; - - if (!i) - return NULL; - - n = i->items_next; - - free(i); - return dns_txt_item_free_all(n); -} - -bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) { - - if (a == b) - return true; - - if (!a != !b) - return false; - - if (!a) - return true; - - if (a->length != b->length) - return false; - - if (memcmp(a->data, b->data, a->length) != 0) - return false; - - return dns_txt_item_equal(a->items_next, b->items_next); -} - -DnsTxtItem *dns_txt_item_copy(DnsTxtItem *first) { - DnsTxtItem *i, *copy = NULL, *end = NULL; - - LIST_FOREACH(items, i, first) { - DnsTxtItem *j; - - j = memdup(i, offsetof(DnsTxtItem, data) + i->length + 1); - if (!j) { - dns_txt_item_free_all(copy); - return NULL; - } - - LIST_INSERT_AFTER(items, copy, end, j); - end = j; - } - - return copy; -} - -static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = { - /* Mnemonics as listed on https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ - [DNSSEC_ALGORITHM_RSAMD5] = "RSAMD5", - [DNSSEC_ALGORITHM_DH] = "DH", - [DNSSEC_ALGORITHM_DSA] = "DSA", - [DNSSEC_ALGORITHM_ECC] = "ECC", - [DNSSEC_ALGORITHM_RSASHA1] = "RSASHA1", - [DNSSEC_ALGORITHM_DSA_NSEC3_SHA1] = "DSA-NSEC3-SHA1", - [DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1] = "RSASHA1-NSEC3-SHA1", - [DNSSEC_ALGORITHM_RSASHA256] = "RSASHA256", - [DNSSEC_ALGORITHM_RSASHA512] = "RSASHA512", - [DNSSEC_ALGORITHM_ECC_GOST] = "ECC-GOST", - [DNSSEC_ALGORITHM_ECDSAP256SHA256] = "ECDSAP256SHA256", - [DNSSEC_ALGORITHM_ECDSAP384SHA384] = "ECDSAP384SHA384", - [DNSSEC_ALGORITHM_INDIRECT] = "INDIRECT", - [DNSSEC_ALGORITHM_PRIVATEDNS] = "PRIVATEDNS", - [DNSSEC_ALGORITHM_PRIVATEOID] = "PRIVATEOID", -}; -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(dnssec_algorithm, int, 255); - -static const char* const dnssec_digest_table[_DNSSEC_DIGEST_MAX_DEFINED] = { - /* Names as listed on https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ - [DNSSEC_DIGEST_SHA1] = "SHA-1", - [DNSSEC_DIGEST_SHA256] = "SHA-256", - [DNSSEC_DIGEST_GOST_R_34_11_94] = "GOST_R_34.11-94", - [DNSSEC_DIGEST_SHA384] = "SHA-384", -}; -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(dnssec_digest, int, 255); diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h deleted file mode 100644 index 42d39a1251..0000000000 --- a/src/resolve/resolved-dns-rr.h +++ /dev/null @@ -1,353 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. - ***/ - -#include <netinet/in.h> - -#include "bitmap.h" -#include "dns-type.h" -#include "hashmap.h" -#include "in-addr-util.h" -#include "list.h" -#include "string-util.h" - -typedef struct DnsResourceKey DnsResourceKey; -typedef struct DnsResourceRecord DnsResourceRecord; -typedef struct DnsTxtItem DnsTxtItem; - -/* DNSKEY RR flags */ -#define DNSKEY_FLAG_SEP (UINT16_C(1) << 0) -#define DNSKEY_FLAG_REVOKE (UINT16_C(1) << 7) -#define DNSKEY_FLAG_ZONE_KEY (UINT16_C(1) << 8) - -/* mDNS RR flags */ -#define MDNS_RR_CACHE_FLUSH (UINT16_C(1) << 15) - -/* DNSSEC algorithm identifiers, see - * http://tools.ietf.org/html/rfc4034#appendix-A.1 and - * https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ -enum { - DNSSEC_ALGORITHM_RSAMD5 = 1, - DNSSEC_ALGORITHM_DH, - DNSSEC_ALGORITHM_DSA, - DNSSEC_ALGORITHM_ECC, - DNSSEC_ALGORITHM_RSASHA1, - DNSSEC_ALGORITHM_DSA_NSEC3_SHA1, - DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, - DNSSEC_ALGORITHM_RSASHA256 = 8, /* RFC 5702 */ - DNSSEC_ALGORITHM_RSASHA512 = 10, /* RFC 5702 */ - DNSSEC_ALGORITHM_ECC_GOST = 12, /* RFC 5933 */ - DNSSEC_ALGORITHM_ECDSAP256SHA256 = 13, /* RFC 6605 */ - DNSSEC_ALGORITHM_ECDSAP384SHA384 = 14, /* RFC 6605 */ - DNSSEC_ALGORITHM_INDIRECT = 252, - DNSSEC_ALGORITHM_PRIVATEDNS, - DNSSEC_ALGORITHM_PRIVATEOID, - _DNSSEC_ALGORITHM_MAX_DEFINED -}; - -/* DNSSEC digest identifiers, see - * https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ -enum { - DNSSEC_DIGEST_SHA1 = 1, - DNSSEC_DIGEST_SHA256 = 2, /* RFC 4509 */ - DNSSEC_DIGEST_GOST_R_34_11_94 = 3, /* RFC 5933 */ - DNSSEC_DIGEST_SHA384 = 4, /* RFC 6605 */ - _DNSSEC_DIGEST_MAX_DEFINED -}; - -/* DNSSEC NSEC3 hash algorithms, see - * https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml */ -enum { - NSEC3_ALGORITHM_SHA1 = 1, - _NSEC3_ALGORITHM_MAX_DEFINED -}; - -struct DnsResourceKey { - unsigned n_ref; /* (unsigned -1) for const keys, see below */ - uint16_t class, type; - char *_name; /* don't access directly, use dns_resource_key_name()! */ -}; - -/* Creates a temporary resource key. This is only useful to quickly - * look up something, without allocating a full DnsResourceKey object - * for it. Note that it is not OK to take references to this kind of - * resource key object. */ -#define DNS_RESOURCE_KEY_CONST(c, t, n) \ - ((DnsResourceKey) { \ - .n_ref = (unsigned) -1, \ - .class = c, \ - .type = t, \ - ._name = (char*) n, \ - }) - - -struct DnsTxtItem { - size_t length; - LIST_FIELDS(DnsTxtItem, items); - uint8_t data[]; -}; - -struct DnsResourceRecord { - unsigned n_ref; - DnsResourceKey *key; - - char *to_string; - - uint32_t ttl; - usec_t expiry; /* RRSIG signature expiry */ - - /* How many labels to strip to determine "signer" of the RRSIG (aka, the zone). -1 if not signed. */ - unsigned n_skip_labels_signer; - /* How many labels to strip to determine "synthesizing source" of this RR, i.e. the wildcard's immediate parent. -1 if not signed. */ - unsigned n_skip_labels_source; - - bool unparseable:1; - - bool wire_format_canonical:1; - void *wire_format; - size_t wire_format_size; - size_t wire_format_rdata_offset; - - union { - struct { - void *data; - size_t data_size; - } generic, opt; - - struct { - uint16_t priority; - uint16_t weight; - uint16_t port; - char *name; - } srv; - - struct { - char *name; - } ptr, ns, cname, dname; - - struct { - char *cpu; - char *os; - } hinfo; - - struct { - DnsTxtItem *items; - } txt, spf; - - struct { - struct in_addr in_addr; - } a; - - struct { - struct in6_addr in6_addr; - } aaaa; - - struct { - char *mname; - char *rname; - uint32_t serial; - uint32_t refresh; - uint32_t retry; - uint32_t expire; - uint32_t minimum; - } soa; - - struct { - uint16_t priority; - char *exchange; - } mx; - - /* https://tools.ietf.org/html/rfc1876 */ - struct { - uint8_t version; - uint8_t size; - uint8_t horiz_pre; - uint8_t vert_pre; - uint32_t latitude; - uint32_t longitude; - uint32_t altitude; - } loc; - - /* https://tools.ietf.org/html/rfc4255#section-3.1 */ - struct { - uint8_t algorithm; - uint8_t fptype; - void *fingerprint; - size_t fingerprint_size; - } sshfp; - - /* http://tools.ietf.org/html/rfc4034#section-2.1 */ - struct { - uint16_t flags; - uint8_t protocol; - uint8_t algorithm; - void* key; - size_t key_size; - } dnskey; - - /* http://tools.ietf.org/html/rfc4034#section-3.1 */ - struct { - uint16_t type_covered; - uint8_t algorithm; - uint8_t labels; - uint32_t original_ttl; - uint32_t expiration; - uint32_t inception; - uint16_t key_tag; - char *signer; - void *signature; - size_t signature_size; - } rrsig; - - /* https://tools.ietf.org/html/rfc4034#section-4.1 */ - struct { - char *next_domain_name; - Bitmap *types; - } nsec; - - /* https://tools.ietf.org/html/rfc4034#section-5.1 */ - struct { - uint16_t key_tag; - uint8_t algorithm; - uint8_t digest_type; - void *digest; - size_t digest_size; - } ds; - - struct { - uint8_t algorithm; - uint8_t flags; - uint16_t iterations; - void *salt; - size_t salt_size; - void *next_hashed_name; - size_t next_hashed_name_size; - Bitmap *types; - } nsec3; - - /* https://tools.ietf.org/html/draft-ietf-dane-protocol-23 */ - struct { - uint8_t cert_usage; - uint8_t selector; - uint8_t matching_type; - void *data; - size_t data_size; - } tlsa; - - /* https://tools.ietf.org/html/rfc6844 */ - struct { - uint8_t flags; - char *tag; - void *value; - size_t value_size; - } caa; - }; -}; - -static inline const void* DNS_RESOURCE_RECORD_RDATA(DnsResourceRecord *rr) { - if (!rr) - return NULL; - - if (!rr->wire_format) - return NULL; - - assert(rr->wire_format_rdata_offset <= rr->wire_format_size); - return (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset; -} - -static inline size_t DNS_RESOURCE_RECORD_RDATA_SIZE(DnsResourceRecord *rr) { - if (!rr) - return 0; - if (!rr->wire_format) - return 0; - - assert(rr->wire_format_rdata_offset <= rr->wire_format_size); - return rr->wire_format_size - rr->wire_format_rdata_offset; -} - -static inline uint8_t DNS_RESOURCE_RECORD_OPT_VERSION_SUPPORTED(DnsResourceRecord *rr) { - assert(rr); - assert(rr->key->type == DNS_TYPE_OPT); - - return ((rr->ttl >> 16) & 0xFF) == 0; -} - -DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name); -DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname); -int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name); -DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name); -DnsResourceKey* dns_resource_key_ref(DnsResourceKey *key); -DnsResourceKey* dns_resource_key_unref(DnsResourceKey *key); -const char* dns_resource_key_name(const DnsResourceKey *key); -bool dns_resource_key_is_address(const DnsResourceKey *key); -int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b); -int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain); -int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain); -int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *soa); - -/* _DNS_{CLASS,TYPE}_STRING_MAX include one byte for NUL, which we use for space instead below. - * DNS_HOSTNAME_MAX does not include the NUL byte, so we need to add 1. */ -#define DNS_RESOURCE_KEY_STRING_MAX (_DNS_CLASS_STRING_MAX + _DNS_TYPE_STRING_MAX + DNS_HOSTNAME_MAX + 1) - -char* dns_resource_key_to_string(const DnsResourceKey *key, char *buf, size_t buf_size); -ssize_t dns_resource_record_payload(DnsResourceRecord *rr, void **out); - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceKey*, dns_resource_key_unref); - -static inline bool dns_key_is_shared(const DnsResourceKey *key) { - return IN_SET(key->type, DNS_TYPE_PTR); -} - -bool dns_resource_key_reduce(DnsResourceKey **a, DnsResourceKey **b); - -DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key); -DnsResourceRecord* dns_resource_record_new_full(uint16_t class, uint16_t type, const char *name); -DnsResourceRecord* dns_resource_record_ref(DnsResourceRecord *rr); -DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr); -int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name); -int dns_resource_record_new_address(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name); -int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecord *b); -const char* dns_resource_record_to_string(DnsResourceRecord *rr); -DnsResourceRecord *dns_resource_record_copy(DnsResourceRecord *rr); -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceRecord*, dns_resource_record_unref); - -int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical); - -int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret); -int dns_resource_record_source(DnsResourceRecord *rr, const char **ret); -int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone); -int dns_resource_record_is_synthetic(DnsResourceRecord *rr); - -int dns_resource_record_clamp_ttl(DnsResourceRecord **rr, uint32_t max_ttl); - -DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i); -bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b); -DnsTxtItem *dns_txt_item_copy(DnsTxtItem *i); - -void dns_resource_record_hash_func(const void *i, struct siphash *state); - -extern const struct hash_ops dns_resource_key_hash_ops; -extern const struct hash_ops dns_resource_record_hash_ops; - -int dnssec_algorithm_to_string_alloc(int i, char **ret); -int dnssec_algorithm_from_string(const char *s) _pure_; - -int dnssec_digest_to_string_alloc(int i, char **ret); -int dnssec_digest_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c deleted file mode 100644 index 8dbc7f623b..0000000000 --- a/src/resolve/resolved-dns-scope.c +++ /dev/null @@ -1,1043 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include <netinet/tcp.h> - -#include "af-list.h" -#include "alloc-util.h" -#include "dns-domain.h" -#include "fd-util.h" -#include "hostname-util.h" -#include "missing.h" -#include "random-util.h" -#include "resolved-dns-scope.h" -#include "resolved-llmnr.h" -#include "resolved-mdns.h" -#include "socket-util.h" -#include "strv.h" - -#define MULTICAST_RATELIMIT_INTERVAL_USEC (1*USEC_PER_SEC) -#define MULTICAST_RATELIMIT_BURST 1000 - -/* After how much time to repeat LLMNR requests, see RFC 4795 Section 7 */ -#define MULTICAST_RESEND_TIMEOUT_MIN_USEC (100 * USEC_PER_MSEC) -#define MULTICAST_RESEND_TIMEOUT_MAX_USEC (1 * USEC_PER_SEC) - -int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int family) { - DnsScope *s; - - assert(m); - assert(ret); - - s = new0(DnsScope, 1); - if (!s) - return -ENOMEM; - - s->manager = m; - s->link = l; - s->protocol = protocol; - s->family = family; - s->resend_timeout = MULTICAST_RESEND_TIMEOUT_MIN_USEC; - - if (protocol == DNS_PROTOCOL_DNS) { - /* Copy DNSSEC mode from the link if it is set there, - * otherwise take the manager's DNSSEC mode. Note that - * we copy this only at scope creation time, and do - * not update it from the on, even if the setting - * changes. */ - - if (l) - s->dnssec_mode = link_get_dnssec_mode(l); - else - s->dnssec_mode = manager_get_dnssec_mode(m); - } else - s->dnssec_mode = DNSSEC_NO; - - LIST_PREPEND(scopes, m->dns_scopes, s); - - dns_scope_llmnr_membership(s, true); - dns_scope_mdns_membership(s, true); - - log_debug("New scope on link %s, protocol %s, family %s", l ? l->name : "*", dns_protocol_to_string(protocol), family == AF_UNSPEC ? "*" : af_to_name(family)); - - /* Enforce ratelimiting for the multicast protocols */ - RATELIMIT_INIT(s->ratelimit, MULTICAST_RATELIMIT_INTERVAL_USEC, MULTICAST_RATELIMIT_BURST); - - *ret = s; - return 0; -} - -static void dns_scope_abort_transactions(DnsScope *s) { - assert(s); - - while (s->transactions) { - DnsTransaction *t = s->transactions; - - /* Abort the transaction, but make sure it is not - * freed while we still look at it */ - - t->block_gc++; - if (DNS_TRANSACTION_IS_LIVE(t->state)) - dns_transaction_complete(t, DNS_TRANSACTION_ABORTED); - t->block_gc--; - - dns_transaction_free(t); - } -} - -DnsScope* dns_scope_free(DnsScope *s) { - DnsResourceRecord *rr; - - if (!s) - return NULL; - - log_debug("Removing scope on link %s, protocol %s, family %s", s->link ? s->link->name : "*", dns_protocol_to_string(s->protocol), s->family == AF_UNSPEC ? "*" : af_to_name(s->family)); - - dns_scope_llmnr_membership(s, false); - dns_scope_mdns_membership(s, false); - dns_scope_abort_transactions(s); - - while (s->query_candidates) - dns_query_candidate_free(s->query_candidates); - - hashmap_free(s->transactions_by_key); - - while ((rr = ordered_hashmap_steal_first(s->conflict_queue))) - dns_resource_record_unref(rr); - - ordered_hashmap_free(s->conflict_queue); - sd_event_source_unref(s->conflict_event_source); - - dns_cache_flush(&s->cache); - dns_zone_flush(&s->zone); - - LIST_REMOVE(scopes, s->manager->dns_scopes, s); - return mfree(s); -} - -DnsServer *dns_scope_get_dns_server(DnsScope *s) { - assert(s); - - if (s->protocol != DNS_PROTOCOL_DNS) - return NULL; - - if (s->link) - return link_get_dns_server(s->link); - else - return manager_get_dns_server(s->manager); -} - -void dns_scope_next_dns_server(DnsScope *s) { - assert(s); - - if (s->protocol != DNS_PROTOCOL_DNS) - return; - - if (s->link) - link_next_dns_server(s->link); - else - manager_next_dns_server(s->manager); -} - -void dns_scope_packet_received(DnsScope *s, usec_t rtt) { - assert(s); - - if (rtt <= s->max_rtt) - return; - - s->max_rtt = rtt; - s->resend_timeout = MIN(MAX(MULTICAST_RESEND_TIMEOUT_MIN_USEC, s->max_rtt * 2), MULTICAST_RESEND_TIMEOUT_MAX_USEC); -} - -void dns_scope_packet_lost(DnsScope *s, usec_t usec) { - assert(s); - - if (s->resend_timeout <= usec) - s->resend_timeout = MIN(s->resend_timeout * 2, MULTICAST_RESEND_TIMEOUT_MAX_USEC); -} - -static int dns_scope_emit_one(DnsScope *s, int fd, DnsPacket *p) { - union in_addr_union addr; - int ifindex = 0, r; - int family; - uint32_t mtu; - - assert(s); - assert(p); - assert(p->protocol == s->protocol); - - if (s->link) { - mtu = s->link->mtu; - ifindex = s->link->ifindex; - } else - mtu = manager_find_mtu(s->manager); - - switch (s->protocol) { - - case DNS_PROTOCOL_DNS: - assert(fd >= 0); - - if (DNS_PACKET_QDCOUNT(p) > 1) - return -EOPNOTSUPP; - - if (p->size > DNS_PACKET_UNICAST_SIZE_MAX) - return -EMSGSIZE; - - if (p->size + UDP_PACKET_HEADER_SIZE > mtu) - return -EMSGSIZE; - - r = manager_write(s->manager, fd, p); - if (r < 0) - return r; - - break; - - case DNS_PROTOCOL_LLMNR: - assert(fd < 0); - - if (DNS_PACKET_QDCOUNT(p) > 1) - return -EOPNOTSUPP; - - if (!ratelimit_test(&s->ratelimit)) - return -EBUSY; - - family = s->family; - - if (family == AF_INET) { - addr.in = LLMNR_MULTICAST_IPV4_ADDRESS; - fd = manager_llmnr_ipv4_udp_fd(s->manager); - } else if (family == AF_INET6) { - addr.in6 = LLMNR_MULTICAST_IPV6_ADDRESS; - fd = manager_llmnr_ipv6_udp_fd(s->manager); - } else - return -EAFNOSUPPORT; - if (fd < 0) - return fd; - - r = manager_send(s->manager, fd, ifindex, family, &addr, LLMNR_PORT, NULL, p); - if (r < 0) - return r; - - break; - - case DNS_PROTOCOL_MDNS: - assert(fd < 0); - - if (!ratelimit_test(&s->ratelimit)) - return -EBUSY; - - family = s->family; - - if (family == AF_INET) { - addr.in = MDNS_MULTICAST_IPV4_ADDRESS; - fd = manager_mdns_ipv4_fd(s->manager); - } else if (family == AF_INET6) { - addr.in6 = MDNS_MULTICAST_IPV6_ADDRESS; - fd = manager_mdns_ipv6_fd(s->manager); - } else - return -EAFNOSUPPORT; - if (fd < 0) - return fd; - - r = manager_send(s->manager, fd, ifindex, family, &addr, MDNS_PORT, NULL, p); - if (r < 0) - return r; - - break; - - default: - return -EAFNOSUPPORT; - } - - return 1; -} - -int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p) { - int r; - - assert(s); - assert(p); - assert(p->protocol == s->protocol); - assert((s->protocol == DNS_PROTOCOL_DNS) == (fd >= 0)); - - do { - /* If there are multiple linked packets, set the TC bit in all but the last of them */ - if (p->more) { - assert(p->protocol == DNS_PROTOCOL_MDNS); - dns_packet_set_flags(p, true, true); - } - - r = dns_scope_emit_one(s, fd, p); - if (r < 0) - return r; - - p = p->more; - } while (p); - - return 0; -} - -static int dns_scope_socket( - DnsScope *s, - int type, - int family, - const union in_addr_union *address, - DnsServer *server, - uint16_t port) { - - _cleanup_close_ int fd = -1; - union sockaddr_union sa = {}; - socklen_t salen; - static const int one = 1; - int ret, r, ifindex; - - assert(s); - - if (server) { - assert(family == AF_UNSPEC); - assert(!address); - - ifindex = dns_server_ifindex(server); - - sa.sa.sa_family = server->family; - if (server->family == AF_INET) { - sa.in.sin_port = htobe16(port); - sa.in.sin_addr = server->address.in; - salen = sizeof(sa.in); - } else if (server->family == AF_INET6) { - sa.in6.sin6_port = htobe16(port); - sa.in6.sin6_addr = server->address.in6; - sa.in6.sin6_scope_id = ifindex; - salen = sizeof(sa.in6); - } else - return -EAFNOSUPPORT; - } else { - assert(family != AF_UNSPEC); - assert(address); - - sa.sa.sa_family = family; - ifindex = s->link ? s->link->ifindex : 0; - - if (family == AF_INET) { - sa.in.sin_port = htobe16(port); - sa.in.sin_addr = address->in; - salen = sizeof(sa.in); - } else if (family == AF_INET6) { - sa.in6.sin6_port = htobe16(port); - sa.in6.sin6_addr = address->in6; - sa.in6.sin6_scope_id = ifindex; - salen = sizeof(sa.in6); - } else - return -EAFNOSUPPORT; - } - - fd = socket(sa.sa.sa_family, type|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (fd < 0) - return -errno; - - if (type == SOCK_STREAM) { - r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); - if (r < 0) - return -errno; - } - - if (s->link) { - be32_t ifindex_be = htobe32(ifindex); - - if (sa.sa.sa_family == AF_INET) { - r = setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex_be, sizeof(ifindex_be)); - if (r < 0) - return -errno; - } else if (sa.sa.sa_family == AF_INET6) { - r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex_be, sizeof(ifindex_be)); - if (r < 0) - return -errno; - } - } - - if (s->protocol == DNS_PROTOCOL_LLMNR) { - /* RFC 4795, section 2.5 requires the TTL to be set to 1 */ - - if (sa.sa.sa_family == AF_INET) { - r = setsockopt(fd, IPPROTO_IP, IP_TTL, &one, sizeof(one)); - if (r < 0) - return -errno; - } else if (sa.sa.sa_family == AF_INET6) { - r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &one, sizeof(one)); - if (r < 0) - return -errno; - } - } - - r = connect(fd, &sa.sa, salen); - if (r < 0 && errno != EINPROGRESS) - return -errno; - - ret = fd; - fd = -1; - - return ret; -} - -int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port) { - return dns_scope_socket(s, SOCK_DGRAM, AF_UNSPEC, NULL, server, port); -} - -int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port) { - return dns_scope_socket(s, SOCK_STREAM, family, address, server, port); -} - -DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain) { - DnsSearchDomain *d; - DnsServer *dns_server; - - assert(s); - assert(domain); - - /* Checks if the specified domain is something to look up on - * this scope. Note that this accepts non-qualified hostnames, - * i.e. those without any search path prefixed yet. */ - - if (ifindex != 0 && (!s->link || s->link->ifindex != ifindex)) - return DNS_SCOPE_NO; - - if ((SD_RESOLVED_FLAGS_MAKE(s->protocol, s->family, 0) & flags) == 0) - return DNS_SCOPE_NO; - - /* Never resolve any loopback hostname or IP address via DNS, - * LLMNR or mDNS. Instead, always rely on synthesized RRs for - * these. */ - if (is_localhost(domain) || - dns_name_endswith(domain, "127.in-addr.arpa") > 0 || - dns_name_equal(domain, "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 DNS_SCOPE_NO; - - /* Never respond to some of the domains listed in RFC6303 */ - if (dns_name_endswith(domain, "0.in-addr.arpa") > 0 || - dns_name_equal(domain, "255.255.255.255.in-addr.arpa") > 0 || - dns_name_equal(domain, "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) - return DNS_SCOPE_NO; - - /* Never respond to some of the domains listed in RFC6761 */ - if (dns_name_endswith(domain, "invalid") > 0) - return DNS_SCOPE_NO; - - /* Always honour search domains for routing queries. Note that - * we return DNS_SCOPE_YES here, rather than just - * DNS_SCOPE_MAYBE, which means wildcard scopes won't be - * considered anymore. */ - LIST_FOREACH(domains, d, dns_scope_get_search_domains(s)) - if (dns_name_endswith(domain, d->name) > 0) - return DNS_SCOPE_YES; - - /* If the DNS server has route-only domains, don't send other requests - * to it. This would be a privacy violation, will most probably fail - * anyway, and adds unnecessary load. */ - dns_server = dns_scope_get_dns_server(s); - if (dns_server && dns_server_limited_domains(dns_server)) - return DNS_SCOPE_NO; - - switch (s->protocol) { - - case DNS_PROTOCOL_DNS: - - /* Exclude link-local IP ranges */ - if (dns_name_endswith(domain, "254.169.in-addr.arpa") == 0 && - dns_name_endswith(domain, "8.e.f.ip6.arpa") == 0 && - dns_name_endswith(domain, "9.e.f.ip6.arpa") == 0 && - dns_name_endswith(domain, "a.e.f.ip6.arpa") == 0 && - dns_name_endswith(domain, "b.e.f.ip6.arpa") == 0 && - /* If networks use .local in their private setups, they are supposed to also add .local to their search - * domains, which we already checked above. Otherwise, we consider .local specific to mDNS and won't - * send such queries ordinary DNS servers. */ - dns_name_endswith(domain, "local") == 0) - return DNS_SCOPE_MAYBE; - - return DNS_SCOPE_NO; - - case DNS_PROTOCOL_MDNS: - if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || - (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) || - (dns_name_endswith(domain, "local") > 0 && /* only resolve names ending in .local via mDNS */ - dns_name_equal(domain, "local") == 0 && /* but not the single-label "local" name itself */ - manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via mDNS */ - return DNS_SCOPE_MAYBE; - - return DNS_SCOPE_NO; - - case DNS_PROTOCOL_LLMNR: - if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || - (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) || - (dns_name_is_single_label(domain) && /* only resolve single label names via LLMNR */ - !is_gateway_hostname(domain) && /* don't resolve "gateway" with LLMNR, let nss-myhostname handle this */ - manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via LLMNR */ - return DNS_SCOPE_MAYBE; - - return DNS_SCOPE_NO; - - default: - assert_not_reached("Unknown scope protocol"); - } -} - -bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key) { - int key_family; - - assert(s); - assert(key); - - /* Check if it makes sense to resolve the specified key on - * this scope. Note that this call assumes as fully qualified - * name, i.e. the search suffixes already appended. */ - - if (key->class != DNS_CLASS_IN) - return false; - - if (s->protocol == DNS_PROTOCOL_DNS) { - - /* On classic DNS, looking up non-address RRs is always - * fine. (Specifically, we want to permit looking up - * DNSKEY and DS records on the root and top-level - * domains.) */ - if (!dns_resource_key_is_address(key)) - return true; - - /* However, we refuse to look up A and AAAA RRs on the - * root and single-label domains, under the assumption - * that those should be resolved via LLMNR or search - * path only, and should not be leaked onto the - * internet. */ - return !(dns_name_is_single_label(dns_resource_key_name(key)) || - dns_name_is_root(dns_resource_key_name(key))); - } - - /* On mDNS and LLMNR, send A and AAAA queries only on the - * respective scopes */ - - key_family = dns_type_to_af(key->type); - if (key_family < 0) - return true; - - return key_family == s->family; -} - -static int dns_scope_multicast_membership(DnsScope *s, bool b, struct in_addr in, struct in6_addr in6) { - int fd; - - assert(s); - assert(s->link); - - if (s->family == AF_INET) { - struct ip_mreqn mreqn = { - .imr_multiaddr = in, - .imr_ifindex = s->link->ifindex, - }; - - fd = manager_llmnr_ipv4_udp_fd(s->manager); - if (fd < 0) - return fd; - - /* Always first try to drop membership before we add - * one. This is necessary on some devices, such as - * veth. */ - if (b) - (void) setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreqn, sizeof(mreqn)); - - if (setsockopt(fd, IPPROTO_IP, b ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, &mreqn, sizeof(mreqn)) < 0) - return -errno; - - } else if (s->family == AF_INET6) { - struct ipv6_mreq mreq = { - .ipv6mr_multiaddr = in6, - .ipv6mr_interface = s->link->ifindex, - }; - - fd = manager_llmnr_ipv6_udp_fd(s->manager); - if (fd < 0) - return fd; - - if (b) - (void) setsockopt(fd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); - - if (setsockopt(fd, IPPROTO_IPV6, b ? IPV6_ADD_MEMBERSHIP : IPV6_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) - return -errno; - } else - return -EAFNOSUPPORT; - - return 0; -} - -int dns_scope_llmnr_membership(DnsScope *s, bool b) { - assert(s); - - if (s->protocol != DNS_PROTOCOL_LLMNR) - return 0; - - return dns_scope_multicast_membership(s, b, LLMNR_MULTICAST_IPV4_ADDRESS, LLMNR_MULTICAST_IPV6_ADDRESS); -} - -int dns_scope_mdns_membership(DnsScope *s, bool b) { - assert(s); - - if (s->protocol != DNS_PROTOCOL_MDNS) - return 0; - - return dns_scope_multicast_membership(s, b, MDNS_MULTICAST_IPV4_ADDRESS, MDNS_MULTICAST_IPV6_ADDRESS); -} - -static int dns_scope_make_reply_packet( - DnsScope *s, - uint16_t id, - int rcode, - DnsQuestion *q, - DnsAnswer *answer, - DnsAnswer *soa, - bool tentative, - DnsPacket **ret) { - - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - int r; - - assert(s); - assert(ret); - - if (dns_question_isempty(q) && - dns_answer_isempty(answer) && - dns_answer_isempty(soa)) - return -EINVAL; - - r = dns_packet_new(&p, s->protocol, 0); - if (r < 0) - return r; - - DNS_PACKET_HEADER(p)->id = id; - DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( - 1 /* qr */, - 0 /* opcode */, - 0 /* c */, - 0 /* tc */, - tentative, - 0 /* (ra) */, - 0 /* (ad) */, - 0 /* (cd) */, - rcode)); - - r = dns_packet_append_question(p, q); - if (r < 0) - return r; - DNS_PACKET_HEADER(p)->qdcount = htobe16(dns_question_size(q)); - - r = dns_packet_append_answer(p, answer); - if (r < 0) - return r; - DNS_PACKET_HEADER(p)->ancount = htobe16(dns_answer_size(answer)); - - r = dns_packet_append_answer(p, soa); - if (r < 0) - return r; - DNS_PACKET_HEADER(p)->arcount = htobe16(dns_answer_size(soa)); - - *ret = p; - p = NULL; - - return 0; -} - -static void dns_scope_verify_conflicts(DnsScope *s, DnsPacket *p) { - DnsResourceRecord *rr; - DnsResourceKey *key; - - assert(s); - assert(p); - - DNS_QUESTION_FOREACH(key, p->question) - dns_zone_verify_conflicts(&s->zone, key); - - DNS_ANSWER_FOREACH(rr, p->answer) - dns_zone_verify_conflicts(&s->zone, rr->key); -} - -void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) { - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL; - _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; - DnsResourceKey *key = NULL; - bool tentative = false; - int r; - - assert(s); - assert(p); - - if (p->protocol != DNS_PROTOCOL_LLMNR) - return; - - if (p->ipproto == IPPROTO_UDP) { - /* Don't accept UDP queries directed to anything but - * the LLMNR multicast addresses. See RFC 4795, - * section 2.5. */ - - if (p->family == AF_INET && !in_addr_equal(AF_INET, &p->destination, (union in_addr_union*) &LLMNR_MULTICAST_IPV4_ADDRESS)) - return; - - if (p->family == AF_INET6 && !in_addr_equal(AF_INET6, &p->destination, (union in_addr_union*) &LLMNR_MULTICAST_IPV6_ADDRESS)) - return; - } - - r = dns_packet_extract(p); - if (r < 0) { - log_debug_errno(r, "Failed to extract resource records from incoming packet: %m"); - return; - } - - if (DNS_PACKET_LLMNR_C(p)) { - /* Somebody notified us about a possible conflict */ - dns_scope_verify_conflicts(s, p); - return; - } - - assert(dns_question_size(p->question) == 1); - key = p->question->keys[0]; - - r = dns_zone_lookup(&s->zone, key, 0, &answer, &soa, &tentative); - if (r < 0) { - log_debug_errno(r, "Failed to lookup key: %m"); - return; - } - if (r == 0) - return; - - if (answer) - dns_answer_order_by_scope(answer, in_addr_is_link_local(p->family, &p->sender) > 0); - - r = dns_scope_make_reply_packet(s, DNS_PACKET_ID(p), DNS_RCODE_SUCCESS, p->question, answer, soa, tentative, &reply); - if (r < 0) { - log_debug_errno(r, "Failed to build reply packet: %m"); - return; - } - - if (stream) { - r = dns_stream_write_packet(stream, reply); - if (r < 0) { - log_debug_errno(r, "Failed to enqueue reply packet: %m"); - return; - } - - /* Let's take an extra reference on this stream, so that it stays around after returning. The reference - * will be dangling until the stream is disconnected, and the default completion handler of the stream - * will then unref the stream and destroy it */ - if (DNS_STREAM_QUEUED(stream)) - dns_stream_ref(stream); - } else { - int fd; - - if (!ratelimit_test(&s->ratelimit)) - return; - - if (p->family == AF_INET) - fd = manager_llmnr_ipv4_udp_fd(s->manager); - else if (p->family == AF_INET6) - fd = manager_llmnr_ipv6_udp_fd(s->manager); - else { - log_debug("Unknown protocol"); - return; - } - if (fd < 0) { - log_debug_errno(fd, "Failed to get reply socket: %m"); - return; - } - - /* Note that we always immediately reply to all LLMNR - * requests, and do not wait any time, since we - * verified uniqueness for all records. Also see RFC - * 4795, Section 2.7 */ - - r = manager_send(s->manager, fd, p->ifindex, p->family, &p->sender, p->sender_port, NULL, reply); - if (r < 0) { - log_debug_errno(r, "Failed to send reply packet: %m"); - return; - } - } -} - -DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok) { - DnsTransaction *t; - - assert(scope); - assert(key); - - /* Try to find an ongoing transaction that is a equal to the - * specified question */ - t = hashmap_get(scope->transactions_by_key, key); - if (!t) - return NULL; - - /* Refuse reusing transactions that completed based on cached - * data instead of a real packet, if that's requested. */ - if (!cache_ok && - IN_SET(t->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_RCODE_FAILURE) && - t->answer_source != DNS_TRANSACTION_NETWORK) - return NULL; - - return t; -} - -static int dns_scope_make_conflict_packet( - DnsScope *s, - DnsResourceRecord *rr, - DnsPacket **ret) { - - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - int r; - - assert(s); - assert(rr); - assert(ret); - - r = dns_packet_new(&p, s->protocol, 0); - if (r < 0) - return r; - - DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( - 0 /* qr */, - 0 /* opcode */, - 1 /* conflict */, - 0 /* tc */, - 0 /* t */, - 0 /* (ra) */, - 0 /* (ad) */, - 0 /* (cd) */, - 0)); - - /* For mDNS, the transaction ID should always be 0 */ - if (s->protocol != DNS_PROTOCOL_MDNS) - random_bytes(&DNS_PACKET_HEADER(p)->id, sizeof(uint16_t)); - - DNS_PACKET_HEADER(p)->qdcount = htobe16(1); - DNS_PACKET_HEADER(p)->arcount = htobe16(1); - - r = dns_packet_append_key(p, rr->key, NULL); - if (r < 0) - return r; - - r = dns_packet_append_rr(p, rr, NULL, NULL); - if (r < 0) - return r; - - *ret = p; - p = NULL; - - return 0; -} - -static int on_conflict_dispatch(sd_event_source *es, usec_t usec, void *userdata) { - DnsScope *scope = userdata; - int r; - - assert(es); - assert(scope); - - scope->conflict_event_source = sd_event_source_unref(scope->conflict_event_source); - - for (;;) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - - rr = ordered_hashmap_steal_first(scope->conflict_queue); - if (!rr) - break; - - r = dns_scope_make_conflict_packet(scope, rr, &p); - if (r < 0) { - log_error_errno(r, "Failed to make conflict packet: %m"); - return 0; - } - - r = dns_scope_emit_udp(scope, -1, p); - if (r < 0) - log_debug_errno(r, "Failed to send conflict packet: %m"); - } - - return 0; -} - -int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr) { - usec_t jitter; - int r; - - assert(scope); - assert(rr); - - /* We don't send these queries immediately. Instead, we queue - * them, and send them after some jitter delay. */ - r = ordered_hashmap_ensure_allocated(&scope->conflict_queue, &dns_resource_key_hash_ops); - if (r < 0) { - log_oom(); - return r; - } - - /* We only place one RR per key in the conflict - * messages, not all of them. That should be enough to - * indicate where there might be a conflict */ - r = ordered_hashmap_put(scope->conflict_queue, rr->key, rr); - if (r == -EEXIST || r == 0) - return 0; - if (r < 0) - return log_debug_errno(r, "Failed to queue conflicting RR: %m"); - - dns_resource_record_ref(rr); - - if (scope->conflict_event_source) - return 0; - - random_bytes(&jitter, sizeof(jitter)); - jitter %= LLMNR_JITTER_INTERVAL_USEC; - - r = sd_event_add_time(scope->manager->event, - &scope->conflict_event_source, - clock_boottime_or_monotonic(), - now(clock_boottime_or_monotonic()) + jitter, - LLMNR_JITTER_INTERVAL_USEC, - on_conflict_dispatch, scope); - if (r < 0) - return log_debug_errno(r, "Failed to add conflict dispatch event: %m"); - - (void) sd_event_source_set_description(scope->conflict_event_source, "scope-conflict"); - - return 0; -} - -void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p) { - unsigned i; - int r; - - assert(scope); - assert(p); - - if (p->protocol != DNS_PROTOCOL_LLMNR) - return; - - if (DNS_PACKET_RRCOUNT(p) <= 0) - return; - - if (DNS_PACKET_LLMNR_C(p) != 0) - return; - - if (DNS_PACKET_LLMNR_T(p) != 0) - return; - - if (manager_our_packet(scope->manager, p)) - return; - - r = dns_packet_extract(p); - if (r < 0) { - log_debug_errno(r, "Failed to extract packet: %m"); - return; - } - - log_debug("Checking for conflicts..."); - - for (i = 0; i < p->answer->n_rrs; i++) { - - /* Check for conflicts against the local zone. If we - * found one, we won't check any further */ - r = dns_zone_check_conflicts(&scope->zone, p->answer->items[i].rr); - if (r != 0) - continue; - - /* Check for conflicts against the local cache. If so, - * send out an advisory query, to inform everybody */ - r = dns_cache_check_conflicts(&scope->cache, p->answer->items[i].rr, p->family, &p->sender); - if (r <= 0) - continue; - - dns_scope_notify_conflict(scope, p->answer->items[i].rr); - } -} - -void dns_scope_dump(DnsScope *s, FILE *f) { - assert(s); - - if (!f) - f = stdout; - - fputs("[Scope protocol=", f); - fputs(dns_protocol_to_string(s->protocol), f); - - if (s->link) { - fputs(" interface=", f); - fputs(s->link->name, f); - } - - if (s->family != AF_UNSPEC) { - fputs(" family=", f); - fputs(af_to_name(s->family), f); - } - - fputs("]\n", f); - - if (!dns_zone_is_empty(&s->zone)) { - fputs("ZONE:\n", f); - dns_zone_dump(&s->zone, f); - } - - if (!dns_cache_is_empty(&s->cache)) { - fputs("CACHE:\n", f); - dns_cache_dump(&s->cache, f); - } -} - -DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s) { - assert(s); - - if (s->protocol != DNS_PROTOCOL_DNS) - return NULL; - - if (s->link) - return s->link->search_domains; - - return s->manager->search_domains; -} - -bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name) { - assert(s); - - if (s->protocol != DNS_PROTOCOL_DNS) - return false; - - return dns_name_is_single_label(name); -} - -bool dns_scope_network_good(DnsScope *s) { - /* 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; - - return manager_routable(s->manager, AF_UNSPEC); -} - -int dns_scope_ifindex(DnsScope *s) { - assert(s); - - if (s->link) - return s->link->ifindex; - - return 0; -} diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h deleted file mode 100644 index 01a83a76b2..0000000000 --- a/src/resolve/resolved-dns-scope.h +++ /dev/null @@ -1,114 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include "list.h" - -typedef struct DnsScope DnsScope; - -#include "resolved-dns-cache.h" -#include "resolved-dns-dnssec.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-query.h" -#include "resolved-dns-search-domain.h" -#include "resolved-dns-server.h" -#include "resolved-dns-stream.h" -#include "resolved-dns-zone.h" -#include "resolved-link.h" - -typedef enum DnsScopeMatch { - DNS_SCOPE_NO, - DNS_SCOPE_MAYBE, - DNS_SCOPE_YES, - _DNS_SCOPE_MATCH_MAX, - _DNS_SCOPE_INVALID = -1 -} DnsScopeMatch; - -struct DnsScope { - Manager *manager; - - DnsProtocol protocol; - int family; - DnssecMode dnssec_mode; - - Link *link; - - DnsCache cache; - DnsZone zone; - - OrderedHashmap *conflict_queue; - sd_event_source *conflict_event_source; - - RateLimit ratelimit; - - usec_t resend_timeout; - usec_t max_rtt; - - LIST_HEAD(DnsQueryCandidate, query_candidates); - - /* Note that we keep track of ongoing transactions in two - * ways: once in a hashmap, indexed by the rr key, and once in - * a linked list. We use the hashmap to quickly find - * transactions we can reuse for a key. But note that there - * might be multiple transactions for the same key (because - * the zone probing can't reuse a transaction answered from - * the zone or the cache), and the hashmap only tracks the - * most recent entry. */ - Hashmap *transactions_by_key; - LIST_HEAD(DnsTransaction, transactions); - - LIST_FIELDS(DnsScope, scopes); -}; - -int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol p, int family); -DnsScope* dns_scope_free(DnsScope *s); - -void dns_scope_packet_received(DnsScope *s, usec_t rtt); -void dns_scope_packet_lost(DnsScope *s, usec_t usec); - -int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p); -int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port); -int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port); - -DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain); -bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key); - -DnsServer *dns_scope_get_dns_server(DnsScope *s); -void dns_scope_next_dns_server(DnsScope *s); - -int dns_scope_llmnr_membership(DnsScope *s, bool b); -int dns_scope_mdns_membership(DnsScope *s, bool b); - -void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p); - -DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok); - -int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr); -void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p); - -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); - -int dns_scope_ifindex(DnsScope *s); diff --git a/src/resolve/resolved-dns-search-domain.c b/src/resolve/resolved-dns-search-domain.c deleted file mode 100644 index 1386e6a17b..0000000000 --- a/src/resolve/resolved-dns-search-domain.c +++ /dev/null @@ -1,225 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 "dns-domain.h" -#include "resolved-dns-search-domain.h" - -int dns_search_domain_new( - Manager *m, - DnsSearchDomain **ret, - DnsSearchDomainType type, - Link *l, - const char *name) { - - _cleanup_free_ char *normalized = NULL; - DnsSearchDomain *d; - int r; - - assert(m); - assert((type == DNS_SEARCH_DOMAIN_LINK) == !!l); - assert(name); - - r = dns_name_normalize(name, &normalized); - if (r < 0) - return r; - - if (l) { - if (l->n_search_domains >= LINK_SEARCH_DOMAINS_MAX) - return -E2BIG; - } else { - if (m->n_search_domains >= MANAGER_SEARCH_DOMAINS_MAX) - return -E2BIG; - } - - d = new0(DnsSearchDomain, 1); - if (!d) - return -ENOMEM; - - d->n_ref = 1; - d->manager = m; - d->type = type; - d->name = normalized; - normalized = NULL; - - switch (type) { - - case DNS_SEARCH_DOMAIN_LINK: - d->link = l; - LIST_APPEND(domains, l->search_domains, d); - l->n_search_domains++; - break; - - case DNS_SERVER_SYSTEM: - LIST_APPEND(domains, m->search_domains, d); - m->n_search_domains++; - break; - - default: - assert_not_reached("Unknown search domain type"); - } - - d->linked = true; - - if (ret) - *ret = d; - - return 0; -} - -DnsSearchDomain* dns_search_domain_ref(DnsSearchDomain *d) { - if (!d) - return NULL; - - assert(d->n_ref > 0); - d->n_ref++; - - return d; -} - -DnsSearchDomain* dns_search_domain_unref(DnsSearchDomain *d) { - if (!d) - return NULL; - - assert(d->n_ref > 0); - d->n_ref--; - - if (d->n_ref > 0) - return NULL; - - free(d->name); - return mfree(d); -} - -void dns_search_domain_unlink(DnsSearchDomain *d) { - assert(d); - assert(d->manager); - - if (!d->linked) - return; - - switch (d->type) { - - case DNS_SEARCH_DOMAIN_LINK: - assert(d->link); - assert(d->link->n_search_domains > 0); - LIST_REMOVE(domains, d->link->search_domains, d); - d->link->n_search_domains--; - break; - - case DNS_SEARCH_DOMAIN_SYSTEM: - assert(d->manager->n_search_domains > 0); - LIST_REMOVE(domains, d->manager->search_domains, d); - d->manager->n_search_domains--; - break; - } - - d->linked = false; - - dns_search_domain_unref(d); -} - -void dns_search_domain_move_back_and_unmark(DnsSearchDomain *d) { - DnsSearchDomain *tail; - - assert(d); - - if (!d->marked) - return; - - d->marked = false; - - if (!d->linked || !d->domains_next) - return; - - switch (d->type) { - - case DNS_SEARCH_DOMAIN_LINK: - assert(d->link); - LIST_FIND_TAIL(domains, d, tail); - LIST_REMOVE(domains, d->link->search_domains, d); - LIST_INSERT_AFTER(domains, d->link->search_domains, tail, d); - break; - - case DNS_SEARCH_DOMAIN_SYSTEM: - LIST_FIND_TAIL(domains, d, tail); - LIST_REMOVE(domains, d->manager->search_domains, d); - LIST_INSERT_AFTER(domains, d->manager->search_domains, tail, d); - break; - - default: - assert_not_reached("Unknown search domain type"); - } -} - -void dns_search_domain_unlink_all(DnsSearchDomain *first) { - DnsSearchDomain *next; - - if (!first) - return; - - next = first->domains_next; - dns_search_domain_unlink(first); - - dns_search_domain_unlink_all(next); -} - -void dns_search_domain_unlink_marked(DnsSearchDomain *first) { - DnsSearchDomain *next; - - if (!first) - return; - - next = first->domains_next; - - if (first->marked) - dns_search_domain_unlink(first); - - dns_search_domain_unlink_marked(next); -} - -void dns_search_domain_mark_all(DnsSearchDomain *first) { - if (!first) - return; - - first->marked = true; - dns_search_domain_mark_all(first->domains_next); -} - -int dns_search_domain_find(DnsSearchDomain *first, const char *name, DnsSearchDomain **ret) { - DnsSearchDomain *d; - int r; - - assert(name); - assert(ret); - - LIST_FOREACH(domains, d, first) { - - r = dns_name_equal(name, d->name); - if (r < 0) - return r; - if (r > 0) { - *ret = d; - return 1; - } - } - - *ret = NULL; - return 0; -} diff --git a/src/resolve/resolved-dns-search-domain.h b/src/resolve/resolved-dns-search-domain.h deleted file mode 100644 index eaacef4edc..0000000000 --- a/src/resolve/resolved-dns-search-domain.h +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include "macro.h" - -typedef struct DnsSearchDomain DnsSearchDomain; - -typedef enum DnsSearchDomainType { - DNS_SEARCH_DOMAIN_SYSTEM, - DNS_SEARCH_DOMAIN_LINK, -} DnsSearchDomainType; - -#include "resolved-link.h" -#include "resolved-manager.h" - -struct DnsSearchDomain { - Manager *manager; - - unsigned n_ref; - - DnsSearchDomainType type; - Link *link; - - char *name; - - bool marked:1; - bool route_only:1; - - bool linked:1; - LIST_FIELDS(DnsSearchDomain, domains); -}; - -int dns_search_domain_new( - Manager *m, - DnsSearchDomain **ret, - DnsSearchDomainType type, - Link *link, - const char *name); - -DnsSearchDomain* dns_search_domain_ref(DnsSearchDomain *d); -DnsSearchDomain* dns_search_domain_unref(DnsSearchDomain *d); - -void dns_search_domain_unlink(DnsSearchDomain *d); -void dns_search_domain_move_back_and_unmark(DnsSearchDomain *d); - -void dns_search_domain_unlink_all(DnsSearchDomain *first); -void dns_search_domain_unlink_marked(DnsSearchDomain *first); -void dns_search_domain_mark_all(DnsSearchDomain *first); - -int dns_search_domain_find(DnsSearchDomain *first, const char *name, DnsSearchDomain **ret); - -static inline const char* DNS_SEARCH_DOMAIN_NAME(DnsSearchDomain *d) { - return d ? d->name : NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsSearchDomain*, dns_search_domain_unref); diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c deleted file mode 100644 index 22c64e8491..0000000000 --- a/src/resolve/resolved-dns-server.c +++ /dev/null @@ -1,796 +0,0 @@ -/*** - 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 <sd-messages.h> - -#include "alloc-util.h" -#include "resolved-dns-server.h" -#include "resolved-dns-stub.h" -#include "resolved-resolv-conf.h" -#include "siphash24.h" -#include "string-table.h" -#include "string-util.h" - -/* After how much time to repeat classic DNS requests */ -#define DNS_TIMEOUT_MIN_USEC (500 * USEC_PER_MSEC) -#define DNS_TIMEOUT_MAX_USEC (5 * USEC_PER_SEC) - -/* The amount of time to wait before retrying with a full feature set */ -#define DNS_SERVER_FEATURE_GRACE_PERIOD_MAX_USEC (6 * USEC_PER_HOUR) -#define DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC (5 * USEC_PER_MINUTE) - -/* The number of times we will attempt a certain feature set before degrading */ -#define DNS_SERVER_FEATURE_RETRY_ATTEMPTS 3 - -int dns_server_new( - Manager *m, - DnsServer **ret, - DnsServerType type, - Link *l, - int family, - const union in_addr_union *in_addr, - int ifindex) { - - DnsServer *s; - - assert(m); - assert((type == DNS_SERVER_LINK) == !!l); - assert(in_addr); - - if (!IN_SET(family, AF_INET, AF_INET6)) - return -EAFNOSUPPORT; - - if (l) { - if (l->n_dns_servers >= LINK_DNS_SERVERS_MAX) - return -E2BIG; - } else { - if (m->n_dns_servers >= MANAGER_DNS_SERVERS_MAX) - return -E2BIG; - } - - s = new0(DnsServer, 1); - if (!s) - return -ENOMEM; - - s->n_ref = 1; - s->manager = m; - s->verified_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID; - s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST; - s->features_grace_period_usec = DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC; - s->received_udp_packet_max = DNS_PACKET_UNICAST_SIZE_MAX; - s->type = type; - s->family = family; - s->address = *in_addr; - s->ifindex = ifindex; - s->resend_timeout = DNS_TIMEOUT_MIN_USEC; - - switch (type) { - - case DNS_SERVER_LINK: - s->link = l; - LIST_APPEND(servers, l->dns_servers, s); - l->n_dns_servers++; - break; - - case DNS_SERVER_SYSTEM: - LIST_APPEND(servers, m->dns_servers, s); - m->n_dns_servers++; - break; - - case DNS_SERVER_FALLBACK: - LIST_APPEND(servers, m->fallback_dns_servers, s); - m->n_dns_servers++; - break; - - default: - assert_not_reached("Unknown server type"); - } - - s->linked = true; - - /* A new DNS server that isn't fallback is added and the one - * we used so far was a fallback one? Then let's try to pick - * the new one */ - if (type != DNS_SERVER_FALLBACK && - m->current_dns_server && - m->current_dns_server->type == DNS_SERVER_FALLBACK) - manager_set_dns_server(m, NULL); - - if (ret) - *ret = s; - - return 0; -} - -DnsServer* dns_server_ref(DnsServer *s) { - if (!s) - return NULL; - - assert(s->n_ref > 0); - s->n_ref++; - - return s; -} - -DnsServer* dns_server_unref(DnsServer *s) { - if (!s) - return NULL; - - assert(s->n_ref > 0); - s->n_ref--; - - if (s->n_ref > 0) - return NULL; - - free(s->server_string); - return mfree(s); -} - -void dns_server_unlink(DnsServer *s) { - assert(s); - assert(s->manager); - - /* This removes the specified server from the linked list of - * servers, but any server might still stay around if it has - * refs, for example from an ongoing transaction. */ - - if (!s->linked) - return; - - switch (s->type) { - - case DNS_SERVER_LINK: - assert(s->link); - assert(s->link->n_dns_servers > 0); - LIST_REMOVE(servers, s->link->dns_servers, s); - s->link->n_dns_servers--; - break; - - case DNS_SERVER_SYSTEM: - assert(s->manager->n_dns_servers > 0); - LIST_REMOVE(servers, s->manager->dns_servers, s); - s->manager->n_dns_servers--; - break; - - case DNS_SERVER_FALLBACK: - assert(s->manager->n_dns_servers > 0); - LIST_REMOVE(servers, s->manager->fallback_dns_servers, s); - s->manager->n_dns_servers--; - break; - } - - s->linked = false; - - if (s->link && s->link->current_dns_server == s) - link_set_dns_server(s->link, NULL); - - if (s->manager->current_dns_server == s) - manager_set_dns_server(s->manager, NULL); - - dns_server_unref(s); -} - -void dns_server_move_back_and_unmark(DnsServer *s) { - DnsServer *tail; - - assert(s); - - if (!s->marked) - return; - - s->marked = false; - - if (!s->linked || !s->servers_next) - return; - - /* Move us to the end of the list, so that the order is - * strictly kept, if we are not at the end anyway. */ - - switch (s->type) { - - case DNS_SERVER_LINK: - assert(s->link); - LIST_FIND_TAIL(servers, s, tail); - LIST_REMOVE(servers, s->link->dns_servers, s); - LIST_INSERT_AFTER(servers, s->link->dns_servers, tail, s); - break; - - case DNS_SERVER_SYSTEM: - LIST_FIND_TAIL(servers, s, tail); - LIST_REMOVE(servers, s->manager->dns_servers, s); - LIST_INSERT_AFTER(servers, s->manager->dns_servers, tail, s); - break; - - case DNS_SERVER_FALLBACK: - LIST_FIND_TAIL(servers, s, tail); - LIST_REMOVE(servers, s->manager->fallback_dns_servers, s); - LIST_INSERT_AFTER(servers, s->manager->fallback_dns_servers, tail, s); - break; - - default: - assert_not_reached("Unknown server type"); - } -} - -static void dns_server_verified(DnsServer *s, DnsServerFeatureLevel level) { - assert(s); - - if (s->verified_feature_level > level) - return; - - if (s->verified_feature_level != level) { - log_debug("Verified we get a response at feature level %s from DNS server %s.", - dns_server_feature_level_to_string(level), - dns_server_string(s)); - s->verified_feature_level = level; - } - - assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0); -} - -static void dns_server_reset_counters(DnsServer *s) { - assert(s); - - s->n_failed_udp = 0; - s->n_failed_tcp = 0; - s->packet_truncated = false; - s->verified_usec = 0; - - /* Note that we do not reset s->packet_bad_opt and s->packet_rrsig_missing here. We reset them only when the - * grace period ends, but not when lowering the possible feature level, as a lower level feature level should - * not make RRSIGs appear or OPT appear, but rather make them disappear. If the reappear anyway, then that's - * indication for a differently broken OPT/RRSIG implementation, and we really don't want to support that - * either. - * - * This is particularly important to deal with certain Belkin routers which break OPT for certain lookups (A), - * but pass traffic through for others (AAAA). If we detect the broken behaviour on one lookup we should not - * reenable it for another, because we cannot validate things anyway, given that the RRSIG/OPT data will be - * incomplete. */ -} - -void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t rtt, size_t size) { - assert(s); - - if (protocol == IPPROTO_UDP) { - if (s->possible_feature_level == level) - s->n_failed_udp = 0; - - /* If the RRSIG data is missing, then we can only validate EDNS0 at max */ - if (s->packet_rrsig_missing && level >= DNS_SERVER_FEATURE_LEVEL_DO) - level = DNS_SERVER_FEATURE_LEVEL_DO - 1; - - /* If the OPT RR got lost, then we can only validate UDP at max */ - if (s->packet_bad_opt && level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) - level = DNS_SERVER_FEATURE_LEVEL_EDNS0 - 1; - - /* Even if we successfully receive a reply to a request announcing support for large packets, - that does not mean we can necessarily receive large packets. */ - if (level == DNS_SERVER_FEATURE_LEVEL_LARGE) - level = DNS_SERVER_FEATURE_LEVEL_LARGE - 1; - - } else if (protocol == IPPROTO_TCP) { - - if (s->possible_feature_level == level) - s->n_failed_tcp = 0; - - /* Successful TCP connections are only useful to verify the TCP feature level. */ - level = DNS_SERVER_FEATURE_LEVEL_TCP; - } - - dns_server_verified(s, level); - - /* Remember the size of the largest UDP packet we received from a server, - we know that we can always announce support for packets with at least - this size. */ - if (protocol == IPPROTO_UDP && s->received_udp_packet_max < size) - s->received_udp_packet_max = size; - - if (s->max_rtt < rtt) { - s->max_rtt = rtt; - s->resend_timeout = CLAMP(s->max_rtt * 2, DNS_TIMEOUT_MIN_USEC, DNS_TIMEOUT_MAX_USEC); - } -} - -void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t usec) { - assert(s); - assert(s->manager); - - if (s->possible_feature_level == level) { - if (protocol == IPPROTO_UDP) - s->n_failed_udp++; - else if (protocol == IPPROTO_TCP) - s->n_failed_tcp++; - } - - if (s->resend_timeout > usec) - return; - - s->resend_timeout = MIN(s->resend_timeout * 2, DNS_TIMEOUT_MAX_USEC); -} - -void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level) { - assert(s); - - /* Invoked whenever we get a packet with TC bit set. */ - - if (s->possible_feature_level != level) - return; - - s->packet_truncated = true; -} - -void dns_server_packet_rrsig_missing(DnsServer *s, DnsServerFeatureLevel level) { - assert(s); - - if (level < DNS_SERVER_FEATURE_LEVEL_DO) - return; - - /* If the RRSIG RRs are missing, we have to downgrade what we previously verified */ - if (s->verified_feature_level >= DNS_SERVER_FEATURE_LEVEL_DO) - s->verified_feature_level = DNS_SERVER_FEATURE_LEVEL_DO-1; - - s->packet_rrsig_missing = true; -} - -void dns_server_packet_bad_opt(DnsServer *s, DnsServerFeatureLevel level) { - assert(s); - - if (level < DNS_SERVER_FEATURE_LEVEL_EDNS0) - return; - - /* If the OPT RR got lost, we have to downgrade what we previously verified */ - if (s->verified_feature_level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) - s->verified_feature_level = DNS_SERVER_FEATURE_LEVEL_EDNS0-1; - - s->packet_bad_opt = true; -} - -void dns_server_packet_rcode_downgrade(DnsServer *s, DnsServerFeatureLevel level) { - assert(s); - - /* Invoked whenever we got a FORMERR, SERVFAIL or NOTIMP rcode from a server and downgrading the feature level - * for the transaction made it go away. In this case we immediately downgrade to the feature level that made - * things work. */ - - if (s->verified_feature_level > level) - s->verified_feature_level = level; - - if (s->possible_feature_level > level) { - s->possible_feature_level = level; - dns_server_reset_counters(s); - } - - log_debug("Downgrading transaction feature level fixed an RCODE error, downgrading server %s too.", dns_server_string(s)); -} - -static bool dns_server_grace_period_expired(DnsServer *s) { - usec_t ts; - - assert(s); - assert(s->manager); - - if (s->verified_usec == 0) - return false; - - assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); - - if (s->verified_usec + s->features_grace_period_usec > ts) - return false; - - s->features_grace_period_usec = MIN(s->features_grace_period_usec * 2, DNS_SERVER_FEATURE_GRACE_PERIOD_MAX_USEC); - - return true; -} - -DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) { - assert(s); - - if (s->possible_feature_level != DNS_SERVER_FEATURE_LEVEL_BEST && - dns_server_grace_period_expired(s)) { - - s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST; - - dns_server_reset_counters(s); - - s->packet_bad_opt = false; - s->packet_rrsig_missing = false; - - log_info("Grace period over, resuming full feature set (%s) for DNS server %s.", - dns_server_feature_level_to_string(s->possible_feature_level), - dns_server_string(s)); - - } else if (s->possible_feature_level <= s->verified_feature_level) - s->possible_feature_level = s->verified_feature_level; - else { - DnsServerFeatureLevel p = s->possible_feature_level; - - if (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && - s->possible_feature_level == DNS_SERVER_FEATURE_LEVEL_TCP) { - - /* We are at the TCP (lowest) level, and we tried a couple of TCP connections, and it didn't - * work. Upgrade back to UDP again. */ - log_debug("Reached maximum number of failed TCP connection attempts, trying UDP again..."); - s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_UDP; - - } else if (s->packet_bad_opt && - s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) { - - /* A reply to one of our EDNS0 queries didn't carry a valid OPT RR, then downgrade to below - * EDNS0 levels. After all, some records generate different responses with and without OPT RR - * in the request. Example: - * https://open.nlnetlabs.nl/pipermail/dnssec-trigger/2014-November/000376.html */ - - log_debug("Server doesn't support EDNS(0) properly, downgrading feature level..."); - s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_UDP; - - } else if (s->packet_rrsig_missing && - s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_DO) { - - /* RRSIG data was missing on a EDNS0 packet with DO bit set. This means the server doesn't - * augment responses with DNSSEC RRs. If so, let's better not ask the server for it anymore, - * after all some servers generate different replies depending if an OPT RR is in the query or - * not. */ - - log_debug("Detected server responses lack RRSIG records, downgrading feature level..."); - s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_EDNS0; - - } else if (s->n_failed_udp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && - s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_UDP) { - - /* We lost too many UDP packets in a row, and are on a feature level of UDP or higher. If the - * packets are lost, maybe the server cannot parse them, hence downgrading sounds like a good - * idea. We might downgrade all the way down to TCP this way. */ - - log_debug("Lost too many UDP packets, downgrading feature level..."); - s->possible_feature_level--; - - } else if (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && - s->packet_truncated && - s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) { - - /* We got too many TCP connection failures in a row, we had at least one truncated packet, and - * are on a feature level above UDP. By downgrading things and getting rid of DNSSEC or EDNS0 - * data we hope to make the packet smaller, so that it still works via UDP given that TCP - * appears not to be a fallback. Note that if we are already at the lowest UDP level, we don't - * go further down, since that's TCP, and TCP failed too often after all. */ - - log_debug("Got too many failed TCP connection failures and truncated UDP packets, downgrading feature level..."); - s->possible_feature_level--; - } - - if (p != s->possible_feature_level) { - - /* We changed the feature level, reset the counting */ - dns_server_reset_counters(s); - - log_warning("Using degraded feature set (%s) for DNS server %s.", - dns_server_feature_level_to_string(s->possible_feature_level), - dns_server_string(s)); - } - } - - return s->possible_feature_level; -} - -int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level) { - size_t packet_size; - bool edns_do; - int r; - - assert(server); - assert(packet); - assert(packet->protocol == DNS_PROTOCOL_DNS); - - /* Fix the OPT field in the packet to match our current feature level. */ - - r = dns_packet_truncate_opt(packet); - if (r < 0) - return r; - - if (level < DNS_SERVER_FEATURE_LEVEL_EDNS0) - return 0; - - edns_do = level >= DNS_SERVER_FEATURE_LEVEL_DO; - - if (level >= DNS_SERVER_FEATURE_LEVEL_LARGE) - packet_size = DNS_PACKET_UNICAST_SIZE_LARGE_MAX; - else - packet_size = server->received_udp_packet_max; - - return dns_packet_append_opt(packet, packet_size, edns_do, 0, NULL); -} - -int dns_server_ifindex(const DnsServer *s) { - assert(s); - - /* The link ifindex always takes precedence */ - if (s->link) - return s->link->ifindex; - - if (s->ifindex > 0) - return s->ifindex; - - return 0; -} - -const char *dns_server_string(DnsServer *server) { - assert(server); - - if (!server->server_string) - (void) in_addr_ifindex_to_string(server->family, &server->address, dns_server_ifindex(server), &server->server_string); - - return strna(server->server_string); -} - -bool dns_server_dnssec_supported(DnsServer *server) { - assert(server); - - /* Returns whether the server supports DNSSEC according to what we know about it */ - - if (server->possible_feature_level < DNS_SERVER_FEATURE_LEVEL_DO) - return false; - - if (server->packet_bad_opt) - return false; - - if (server->packet_rrsig_missing) - return false; - - /* DNSSEC servers need to support TCP properly (see RFC5966), if they don't, we assume DNSSEC is borked too */ - if (server->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS) - return false; - - return true; -} - -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; -} - -bool dns_server_limited_domains(DnsServer *server) { - DnsSearchDomain *domain; - bool domain_restricted = false; - - /* Check if the server has route-only domains without ~., i. e. whether - * it should only be used for particular domains */ - if (!server->link) - return false; - - LIST_FOREACH(domains, domain, server->link->search_domains) - if (domain->route_only) { - domain_restricted = true; - /* ~. means "any domain", thus it is a global server */ - if (dns_name_is_root(DNS_SEARCH_DOMAIN_NAME(domain))) - return false; - } - - return domain_restricted; -} - -static void dns_server_hash_func(const void *p, struct siphash *state) { - const DnsServer *s = p; - - assert(s); - - siphash24_compress(&s->family, sizeof(s->family), state); - siphash24_compress(&s->address, FAMILY_ADDRESS_SIZE(s->family), state); - siphash24_compress(&s->ifindex, sizeof(s->ifindex), state); -} - -static int dns_server_compare_func(const void *a, const void *b) { - const DnsServer *x = a, *y = b; - int r; - - if (x->family < y->family) - return -1; - if (x->family > y->family) - return 1; - - r = memcmp(&x->address, &y->address, FAMILY_ADDRESS_SIZE(x->family)); - if (r != 0) - return r; - - if (x->ifindex < y->ifindex) - return -1; - if (x->ifindex > y->ifindex) - return 1; - - return 0; -} - -const struct hash_ops dns_server_hash_ops = { - .hash = dns_server_hash_func, - .compare = dns_server_compare_func -}; - -void dns_server_unlink_all(DnsServer *first) { - DnsServer *next; - - if (!first) - return; - - next = first->servers_next; - dns_server_unlink(first); - - dns_server_unlink_all(next); -} - -void dns_server_unlink_marked(DnsServer *first) { - DnsServer *next; - - if (!first) - return; - - next = first->servers_next; - - if (first->marked) - dns_server_unlink(first); - - dns_server_unlink_marked(next); -} - -void dns_server_mark_all(DnsServer *first) { - if (!first) - return; - - first->marked = true; - dns_server_mark_all(first->servers_next); -} - -DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr, int ifindex) { - DnsServer *s; - - LIST_FOREACH(servers, s, first) - if (s->family == family && in_addr_equal(family, &s->address, in_addr) > 0 && s->ifindex == ifindex) - return s; - - return NULL; -} - -DnsServer *manager_get_first_dns_server(Manager *m, DnsServerType t) { - assert(m); - - switch (t) { - - case DNS_SERVER_SYSTEM: - return m->dns_servers; - - case DNS_SERVER_FALLBACK: - return m->fallback_dns_servers; - - default: - return NULL; - } -} - -DnsServer *manager_set_dns_server(Manager *m, DnsServer *s) { - assert(m); - - if (m->current_dns_server == s) - return s; - - if (s) - log_info("Switching to %s DNS server %s.", - dns_server_type_to_string(s->type), - dns_server_string(s)); - - dns_server_unref(m->current_dns_server); - m->current_dns_server = dns_server_ref(s); - - if (m->unicast_scope) - dns_cache_flush(&m->unicast_scope->cache); - - return s; -} - -DnsServer *manager_get_dns_server(Manager *m) { - Link *l; - assert(m); - - /* Try to read updates resolv.conf */ - manager_read_resolv_conf(m); - - /* If no DNS server was chosen so far, pick the first one */ - if (!m->current_dns_server) - manager_set_dns_server(m, m->dns_servers); - - if (!m->current_dns_server) { - bool found = false; - Iterator i; - - /* No DNS servers configured, let's see if there are - * any on any links. If not, we use the fallback - * servers */ - - HASHMAP_FOREACH(l, m->links, i) - if (l->dns_servers) { - found = true; - break; - } - - if (!found) - manager_set_dns_server(m, m->fallback_dns_servers); - } - - return m->current_dns_server; -} - -void manager_next_dns_server(Manager *m) { - assert(m); - - /* If there's currently no DNS server set, then the next - * manager_get_dns_server() will find one */ - if (!m->current_dns_server) - return; - - /* Change to the next one, but make sure to follow the linked - * list only if the server is still linked. */ - if (m->current_dns_server->linked && m->current_dns_server->servers_next) { - manager_set_dns_server(m, m->current_dns_server->servers_next); - return; - } - - /* If there was no next one, then start from the beginning of - * the list */ - if (m->current_dns_server->type == DNS_SERVER_FALLBACK) - manager_set_dns_server(m, m->fallback_dns_servers); - else - manager_set_dns_server(m, m->dns_servers); -} - -bool dns_server_address_valid(int family, const union in_addr_union *sa) { - - /* Refuses the 0 IP addresses as well as 127.0.0.53 (which is our own DNS stub) */ - - if (in_addr_is_null(family, sa)) - return false; - - if (family == AF_INET && sa->in.s_addr == htobe32(INADDR_DNS_STUB)) - return false; - - return true; -} - -static const char* const dns_server_type_table[_DNS_SERVER_TYPE_MAX] = { - [DNS_SERVER_SYSTEM] = "system", - [DNS_SERVER_FALLBACK] = "fallback", - [DNS_SERVER_LINK] = "link", -}; -DEFINE_STRING_TABLE_LOOKUP(dns_server_type, DnsServerType); - -static const char* const dns_server_feature_level_table[_DNS_SERVER_FEATURE_LEVEL_MAX] = { - [DNS_SERVER_FEATURE_LEVEL_TCP] = "TCP", - [DNS_SERVER_FEATURE_LEVEL_UDP] = "UDP", - [DNS_SERVER_FEATURE_LEVEL_EDNS0] = "UDP+EDNS0", - [DNS_SERVER_FEATURE_LEVEL_DO] = "UDP+EDNS0+DO", - [DNS_SERVER_FEATURE_LEVEL_LARGE] = "UDP+EDNS0+DO+LARGE", -}; -DEFINE_STRING_TABLE_LOOKUP(dns_server_feature_level, DnsServerFeatureLevel); diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h deleted file mode 100644 index 83e288a202..0000000000 --- a/src/resolve/resolved-dns-server.h +++ /dev/null @@ -1,149 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include "in-addr-util.h" - -typedef struct DnsServer DnsServer; - -typedef enum DnsServerType { - DNS_SERVER_SYSTEM, - DNS_SERVER_FALLBACK, - DNS_SERVER_LINK, -} DnsServerType; -#define _DNS_SERVER_TYPE_MAX (DNS_SERVER_LINK + 1) - -const char* dns_server_type_to_string(DnsServerType i) _const_; -DnsServerType dns_server_type_from_string(const char *s) _pure_; - -typedef enum DnsServerFeatureLevel { - DNS_SERVER_FEATURE_LEVEL_TCP, - DNS_SERVER_FEATURE_LEVEL_UDP, - DNS_SERVER_FEATURE_LEVEL_EDNS0, - DNS_SERVER_FEATURE_LEVEL_DO, - DNS_SERVER_FEATURE_LEVEL_LARGE, - _DNS_SERVER_FEATURE_LEVEL_MAX, - _DNS_SERVER_FEATURE_LEVEL_INVALID = -1 -} DnsServerFeatureLevel; - -#define DNS_SERVER_FEATURE_LEVEL_WORST 0 -#define DNS_SERVER_FEATURE_LEVEL_BEST (_DNS_SERVER_FEATURE_LEVEL_MAX - 1) - -const char* dns_server_feature_level_to_string(int i) _const_; -int dns_server_feature_level_from_string(const char *s) _pure_; - -#include "resolved-link.h" -#include "resolved-manager.h" - -struct DnsServer { - Manager *manager; - - unsigned n_ref; - - DnsServerType type; - Link *link; - - int family; - union in_addr_union address; - int ifindex; /* for IPv6 link-local DNS servers */ - - char *server_string; - - usec_t resend_timeout; - usec_t max_rtt; - - DnsServerFeatureLevel verified_feature_level; - DnsServerFeatureLevel possible_feature_level; - - size_t received_udp_packet_max; - - unsigned n_failed_udp; - unsigned n_failed_tcp; - - bool packet_truncated:1; - bool packet_bad_opt:1; - bool packet_rrsig_missing:1; - - 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; - - /* If linked is set, then this server appears in the servers linked list */ - bool linked:1; - LIST_FIELDS(DnsServer, servers); -}; - -int dns_server_new( - Manager *m, - DnsServer **ret, - DnsServerType type, - Link *link, - int family, - const union in_addr_union *address, - int ifindex); - -DnsServer* dns_server_ref(DnsServer *s); -DnsServer* dns_server_unref(DnsServer *s); - -void dns_server_unlink(DnsServer *s); -void dns_server_move_back_and_unmark(DnsServer *s); - -void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t rtt, size_t size); -void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t usec); -void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level); -void dns_server_packet_rrsig_missing(DnsServer *s, DnsServerFeatureLevel level); -void dns_server_packet_bad_opt(DnsServer *s, DnsServerFeatureLevel level); -void dns_server_packet_rcode_downgrade(DnsServer *s, DnsServerFeatureLevel level); - -DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s); - -int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level); - -const char *dns_server_string(DnsServer *server); -int dns_server_ifindex(const DnsServer *s); - -bool dns_server_dnssec_supported(DnsServer *server); - -void dns_server_warn_downgrade(DnsServer *server); - -bool dns_server_limited_domains(DnsServer *server); - -DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr, int ifindex); - -void dns_server_unlink_all(DnsServer *first); -void dns_server_unlink_marked(DnsServer *first); -void dns_server_mark_all(DnsServer *first); - -DnsServer *manager_get_first_dns_server(Manager *m, DnsServerType t); - -DnsServer *manager_set_dns_server(Manager *m, DnsServer *s); -DnsServer *manager_get_dns_server(Manager *m); -void manager_next_dns_server(Manager *m); - -bool dns_server_address_valid(int family, const union in_addr_union *sa); - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref); - -extern const struct hash_ops dns_server_hash_ops; diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c deleted file mode 100644 index 878bae47dc..0000000000 --- a/src/resolve/resolved-dns-stream.c +++ /dev/null @@ -1,418 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include <netinet/tcp.h> - -#include "alloc-util.h" -#include "fd-util.h" -#include "io-util.h" -#include "missing.h" -#include "resolved-dns-stream.h" - -#define DNS_STREAM_TIMEOUT_USEC (10 * USEC_PER_SEC) -#define DNS_STREAMS_MAX 128 - -static void dns_stream_stop(DnsStream *s) { - assert(s); - - s->io_event_source = sd_event_source_unref(s->io_event_source); - s->timeout_event_source = sd_event_source_unref(s->timeout_event_source); - s->fd = safe_close(s->fd); -} - -static int dns_stream_update_io(DnsStream *s) { - int f = 0; - - assert(s); - - if (s->write_packet && s->n_written < sizeof(s->write_size) + s->write_packet->size) - f |= EPOLLOUT; - if (!s->read_packet || s->n_read < sizeof(s->read_size) + s->read_packet->size) - f |= EPOLLIN; - - return sd_event_source_set_io_events(s->io_event_source, f); -} - -static int dns_stream_complete(DnsStream *s, int error) { - assert(s); - - dns_stream_stop(s); - - if (s->complete) - s->complete(s, error); - else /* the default action if no completion function is set is to close the stream */ - dns_stream_unref(s); - - return 0; -} - -static int dns_stream_identify(DnsStream *s) { - union { - struct cmsghdr header; /* For alignment */ - uint8_t buffer[CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo)) - + EXTRA_CMSG_SPACE /* kernel appears to require extra space */]; - } control; - struct msghdr mh = {}; - struct cmsghdr *cmsg; - socklen_t sl; - int r; - - assert(s); - - if (s->identified) - return 0; - - /* Query the local side */ - s->local_salen = sizeof(s->local); - r = getsockname(s->fd, &s->local.sa, &s->local_salen); - if (r < 0) - return -errno; - if (s->local.sa.sa_family == AF_INET6 && s->ifindex <= 0) - s->ifindex = s->local.in6.sin6_scope_id; - - /* Query the remote side */ - s->peer_salen = sizeof(s->peer); - r = getpeername(s->fd, &s->peer.sa, &s->peer_salen); - if (r < 0) - return -errno; - if (s->peer.sa.sa_family == AF_INET6 && s->ifindex <= 0) - s->ifindex = s->peer.in6.sin6_scope_id; - - /* Check consistency */ - assert(s->peer.sa.sa_family == s->local.sa.sa_family); - assert(IN_SET(s->peer.sa.sa_family, AF_INET, AF_INET6)); - - /* Query connection meta information */ - sl = sizeof(control); - if (s->peer.sa.sa_family == AF_INET) { - r = getsockopt(s->fd, IPPROTO_IP, IP_PKTOPTIONS, &control, &sl); - if (r < 0) - return -errno; - } else if (s->peer.sa.sa_family == AF_INET6) { - - r = getsockopt(s->fd, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, &control, &sl); - if (r < 0) - return -errno; - } else - return -EAFNOSUPPORT; - - mh.msg_control = &control; - mh.msg_controllen = sl; - - CMSG_FOREACH(cmsg, &mh) { - - if (cmsg->cmsg_level == IPPROTO_IPV6) { - assert(s->peer.sa.sa_family == AF_INET6); - - switch (cmsg->cmsg_type) { - - case IPV6_PKTINFO: { - struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg); - - if (s->ifindex <= 0) - s->ifindex = i->ipi6_ifindex; - break; - } - - case IPV6_HOPLIMIT: - s->ttl = *(int *) CMSG_DATA(cmsg); - break; - } - - } else if (cmsg->cmsg_level == IPPROTO_IP) { - assert(s->peer.sa.sa_family == AF_INET); - - switch (cmsg->cmsg_type) { - - case IP_PKTINFO: { - struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg); - - if (s->ifindex <= 0) - s->ifindex = i->ipi_ifindex; - break; - } - - case IP_TTL: - s->ttl = *(int *) CMSG_DATA(cmsg); - break; - } - } - } - - /* The Linux kernel sets the interface index to the loopback - * device if the connection came from the local host since it - * avoids the routing table in such a case. Let's unset the - * interface index in such a case. */ - if (s->ifindex == LOOPBACK_IFINDEX) - s->ifindex = 0; - - /* If we don't know the interface index still, we look for the - * first local interface with a matching address. Yuck! */ - if (s->ifindex <= 0) - s->ifindex = manager_find_ifindex(s->manager, s->local.sa.sa_family, s->local.sa.sa_family == AF_INET ? (union in_addr_union*) &s->local.in.sin_addr : (union in_addr_union*) &s->local.in6.sin6_addr); - - if (s->protocol == DNS_PROTOCOL_LLMNR && s->ifindex > 0) { - uint32_t ifindex = htobe32(s->ifindex); - - /* Make sure all packets for this connection are sent on the same interface */ - if (s->local.sa.sa_family == AF_INET) { - r = setsockopt(s->fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex)); - if (r < 0) - log_debug_errno(errno, "Failed to invoke IP_UNICAST_IF: %m"); - } else if (s->local.sa.sa_family == AF_INET6) { - r = setsockopt(s->fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex)); - if (r < 0) - log_debug_errno(errno, "Failed to invoke IPV6_UNICAST_IF: %m"); - } - } - - s->identified = true; - - return 0; -} - -static int on_stream_timeout(sd_event_source *es, usec_t usec, void *userdata) { - DnsStream *s = userdata; - - assert(s); - - return dns_stream_complete(s, ETIMEDOUT); -} - -static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *userdata) { - DnsStream *s = userdata; - int r; - - assert(s); - - r = dns_stream_identify(s); - if (r < 0) - return dns_stream_complete(s, -r); - - if ((revents & EPOLLOUT) && - s->write_packet && - s->n_written < sizeof(s->write_size) + s->write_packet->size) { - - struct iovec iov[2]; - ssize_t ss; - - iov[0].iov_base = &s->write_size; - iov[0].iov_len = sizeof(s->write_size); - iov[1].iov_base = DNS_PACKET_DATA(s->write_packet); - iov[1].iov_len = s->write_packet->size; - - IOVEC_INCREMENT(iov, 2, s->n_written); - - ss = writev(fd, iov, 2); - if (ss < 0) { - if (errno != EINTR && errno != EAGAIN) - return dns_stream_complete(s, errno); - } else - s->n_written += ss; - - /* Are we done? If so, disable the event source for EPOLLOUT */ - if (s->n_written >= sizeof(s->write_size) + s->write_packet->size) { - r = dns_stream_update_io(s); - if (r < 0) - return dns_stream_complete(s, -r); - } - } - - if ((revents & (EPOLLIN|EPOLLHUP|EPOLLRDHUP)) && - (!s->read_packet || - s->n_read < sizeof(s->read_size) + s->read_packet->size)) { - - if (s->n_read < sizeof(s->read_size)) { - ssize_t ss; - - ss = read(fd, (uint8_t*) &s->read_size + s->n_read, sizeof(s->read_size) - s->n_read); - if (ss < 0) { - if (errno != EINTR && errno != EAGAIN) - return dns_stream_complete(s, errno); - } else if (ss == 0) - return dns_stream_complete(s, ECONNRESET); - else - s->n_read += ss; - } - - if (s->n_read >= sizeof(s->read_size)) { - - if (be16toh(s->read_size) < DNS_PACKET_HEADER_SIZE) - return dns_stream_complete(s, EBADMSG); - - if (s->n_read < sizeof(s->read_size) + be16toh(s->read_size)) { - ssize_t ss; - - if (!s->read_packet) { - r = dns_packet_new(&s->read_packet, s->protocol, be16toh(s->read_size)); - if (r < 0) - return dns_stream_complete(s, -r); - - s->read_packet->size = be16toh(s->read_size); - s->read_packet->ipproto = IPPROTO_TCP; - s->read_packet->family = s->peer.sa.sa_family; - s->read_packet->ttl = s->ttl; - s->read_packet->ifindex = s->ifindex; - - if (s->read_packet->family == AF_INET) { - s->read_packet->sender.in = s->peer.in.sin_addr; - s->read_packet->sender_port = be16toh(s->peer.in.sin_port); - s->read_packet->destination.in = s->local.in.sin_addr; - s->read_packet->destination_port = be16toh(s->local.in.sin_port); - } else { - assert(s->read_packet->family == AF_INET6); - s->read_packet->sender.in6 = s->peer.in6.sin6_addr; - s->read_packet->sender_port = be16toh(s->peer.in6.sin6_port); - s->read_packet->destination.in6 = s->local.in6.sin6_addr; - s->read_packet->destination_port = be16toh(s->local.in6.sin6_port); - - if (s->read_packet->ifindex == 0) - s->read_packet->ifindex = s->peer.in6.sin6_scope_id; - if (s->read_packet->ifindex == 0) - s->read_packet->ifindex = s->local.in6.sin6_scope_id; - } - } - - ss = read(fd, - (uint8_t*) DNS_PACKET_DATA(s->read_packet) + s->n_read - sizeof(s->read_size), - sizeof(s->read_size) + be16toh(s->read_size) - s->n_read); - if (ss < 0) { - if (errno != EINTR && errno != EAGAIN) - return dns_stream_complete(s, errno); - } else if (ss == 0) - return dns_stream_complete(s, ECONNRESET); - else - s->n_read += ss; - } - - /* Are we done? If so, disable the event source for EPOLLIN */ - if (s->n_read >= sizeof(s->read_size) + be16toh(s->read_size)) { - r = dns_stream_update_io(s); - if (r < 0) - return dns_stream_complete(s, -r); - - /* If there's a packet handler - * installed, call that. Note that - * this is optional... */ - if (s->on_packet) - return s->on_packet(s); - } - } - } - - if ((s->write_packet && s->n_written >= sizeof(s->write_size) + s->write_packet->size) && - (s->read_packet && s->n_read >= sizeof(s->read_size) + s->read_packet->size)) - return dns_stream_complete(s, 0); - - return 0; -} - -DnsStream *dns_stream_unref(DnsStream *s) { - if (!s) - return NULL; - - assert(s->n_ref > 0); - s->n_ref--; - - if (s->n_ref > 0) - return NULL; - - dns_stream_stop(s); - - if (s->manager) { - LIST_REMOVE(streams, s->manager->dns_streams, s); - s->manager->n_dns_streams--; - } - - dns_packet_unref(s->write_packet); - dns_packet_unref(s->read_packet); - - return mfree(s); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_unref); - -DnsStream *dns_stream_ref(DnsStream *s) { - if (!s) - return NULL; - - assert(s->n_ref > 0); - s->n_ref++; - - return s; -} - -int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { - _cleanup_(dns_stream_unrefp) DnsStream *s = NULL; - int r; - - assert(m); - assert(fd >= 0); - - if (m->n_dns_streams > DNS_STREAMS_MAX) - return -EBUSY; - - s = new0(DnsStream, 1); - if (!s) - return -ENOMEM; - - s->n_ref = 1; - s->fd = -1; - s->protocol = protocol; - - r = sd_event_add_io(m->event, &s->io_event_source, fd, EPOLLIN, on_stream_io, s); - if (r < 0) - return r; - - (void) sd_event_source_set_description(s->io_event_source, "dns-stream-io"); - - r = sd_event_add_time( - m->event, - &s->timeout_event_source, - clock_boottime_or_monotonic(), - now(clock_boottime_or_monotonic()) + DNS_STREAM_TIMEOUT_USEC, 0, - on_stream_timeout, s); - if (r < 0) - return r; - - (void) sd_event_source_set_description(s->timeout_event_source, "dns-stream-timeout"); - - LIST_PREPEND(streams, m->dns_streams, s); - s->manager = m; - s->fd = fd; - m->n_dns_streams++; - - *ret = s; - s = NULL; - - return 0; -} - -int dns_stream_write_packet(DnsStream *s, DnsPacket *p) { - assert(s); - - if (s->write_packet) - return -EBUSY; - - s->write_packet = dns_packet_ref(p); - s->write_size = htobe16(p->size); - s->n_written = 0; - - return dns_stream_update_io(s); -} diff --git a/src/resolve/resolved-dns-stream.h b/src/resolve/resolved-dns-stream.h deleted file mode 100644 index 4cdb4f6806..0000000000 --- a/src/resolve/resolved-dns-stream.h +++ /dev/null @@ -1,81 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include "socket-util.h" - -typedef struct DnsStream DnsStream; - -#include "resolved-dns-packet.h" -#include "resolved-dns-transaction.h" -#include "resolved-manager.h" - -/* Streams are used by three subsystems: - * - * 1. The normal transaction logic when doing a DNS or LLMNR lookup via TCP - * 2. The LLMNR logic when accepting a TCP-based lookup - * 3. The DNS stub logic when accepting a TCP-based lookup - */ - -struct DnsStream { - Manager *manager; - int n_ref; - - DnsProtocol protocol; - - int fd; - union sockaddr_union peer; - socklen_t peer_salen; - union sockaddr_union local; - socklen_t local_salen; - int ifindex; - uint32_t ttl; - bool identified; - - sd_event_source *io_event_source; - sd_event_source *timeout_event_source; - - be16_t write_size, read_size; - DnsPacket *write_packet, *read_packet; - size_t n_written, n_read; - - int (*on_packet)(DnsStream *s); - int (*complete)(DnsStream *s, int error); - - DnsTransaction *transaction; /* when used by the transaction logic */ - DnsQuery *query; /* when used by the DNS stub logic */ - - LIST_FIELDS(DnsStream, streams); -}; - -int dns_stream_new(Manager *m, DnsStream **s, DnsProtocol protocol, int fd); -DnsStream *dns_stream_unref(DnsStream *s); -DnsStream *dns_stream_ref(DnsStream *s); - -int dns_stream_write_packet(DnsStream *s, DnsPacket *p); - -static inline bool DNS_STREAM_QUEUED(DnsStream *s) { - assert(s); - - if (s->fd < 0) /* already stopped? */ - return false; - - return !!s->write_packet; -} diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c deleted file mode 100644 index e76de6c06a..0000000000 --- a/src/resolve/resolved-dns-stub.c +++ /dev/null @@ -1,538 +0,0 @@ -/*** - 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 "resolved-dns-stub.h" -#include "socket-util.h" - -/* The MTU of the loopback device is 64K on Linux, advertise that as maximum datagram size, but subtract the Ethernet, - * IP and UDP header sizes */ -#define ADVERTISE_DATAGRAM_SIZE_MAX (65536U-14U-20U-8U) - -static int manager_dns_stub_udp_fd(Manager *m); -static int manager_dns_stub_tcp_fd(Manager *m); - -static int dns_stub_make_reply_packet( - uint16_t id, - int rcode, - DnsQuestion *q, - DnsAnswer *answer, - bool add_opt, /* add an OPT RR to this packet */ - bool edns0_do, /* set the EDNS0 DNSSEC OK bit */ - bool ad, /* set the DNSSEC authenticated data bit */ - DnsPacket **ret) { - - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - DnsResourceRecord *rr; - unsigned c = 0; - int r; - - /* Note that we don't bother with any additional RRs, as this is stub is for local lookups only, and hence - * roundtrips aren't expensive. */ - - r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0); - if (r < 0) - return r; - - /* If the client didn't do EDNS, clamp the rcode to 4 bit */ - if (!add_opt && rcode > 0xF) - rcode = DNS_RCODE_SERVFAIL; - - DNS_PACKET_HEADER(p)->id = id; - DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( - 1 /* qr */, - 0 /* opcode */, - 0 /* aa */, - 0 /* tc */, - 1 /* rd */, - 1 /* ra */, - ad /* ad */, - 0 /* cd */, - rcode)); - - r = dns_packet_append_question(p, q); - if (r < 0) - return r; - DNS_PACKET_HEADER(p)->qdcount = htobe16(dns_question_size(q)); - - DNS_ANSWER_FOREACH(rr, answer) { - r = dns_question_matches_rr(q, rr, NULL); - if (r < 0) - return r; - if (r > 0) - goto add; - - r = dns_question_matches_cname_or_dname(q, rr, NULL); - if (r < 0) - return r; - if (r > 0) - goto add; - - continue; - add: - r = dns_packet_append_rr(p, rr, NULL, NULL); - if (r < 0) - return r; - - c++; - } - DNS_PACKET_HEADER(p)->ancount = htobe16(c); - - if (add_opt) { - r = dns_packet_append_opt(p, ADVERTISE_DATAGRAM_SIZE_MAX, edns0_do, rcode, NULL); - if (r < 0) - return r; - } - - *ret = p; - p = NULL; - - return 0; -} - -static void dns_stub_detach_stream(DnsStream *s) { - assert(s); - - s->complete = NULL; - s->on_packet = NULL; - s->query = NULL; -} - -static int dns_stub_send(Manager *m, DnsStream *s, DnsPacket *p, DnsPacket *reply) { - int r; - - assert(m); - assert(p); - assert(reply); - - if (s) - r = dns_stream_write_packet(s, reply); - else { - int fd; - - /* Truncate the message to the right size */ - if (reply->size > DNS_PACKET_PAYLOAD_SIZE_MAX(p)) { - dns_packet_truncate(reply, DNS_PACKET_UNICAST_SIZE_MAX); - DNS_PACKET_HEADER(reply)->flags = htobe16(be16toh(DNS_PACKET_HEADER(reply)->flags) | DNS_PACKET_FLAG_TC); - } - - fd = manager_dns_stub_udp_fd(m); - if (fd < 0) - return log_debug_errno(fd, "Failed to get reply socket: %m"); - - /* Note that it is essential here that we explicitly choose the source IP address for this packet. This - * is because otherwise the kernel will choose it automatically based on the routing table and will - * thus pick 127.0.0.1 rather than 127.0.0.53. */ - - r = manager_send(m, fd, LOOPBACK_IFINDEX, p->family, &p->sender, p->sender_port, &p->destination, reply); - } - if (r < 0) - return log_debug_errno(r, "Failed to send reply packet: %m"); - - return 0; -} - -static int dns_stub_send_failure(Manager *m, DnsStream *s, DnsPacket *p, int rcode) { - _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; - int r; - - assert(m); - assert(p); - - r = dns_stub_make_reply_packet(DNS_PACKET_ID(p), rcode, p->question, NULL, !!p->opt, DNS_PACKET_DO(p), false, &reply); - if (r < 0) - return log_debug_errno(r, "Failed to build failure packet: %m"); - - return dns_stub_send(m, s, p, reply); -} - -static void dns_stub_query_complete(DnsQuery *q) { - int r; - - assert(q); - assert(q->request_dns_packet); - - switch (q->state) { - - case DNS_TRANSACTION_SUCCESS: { - _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; - - r = dns_stub_make_reply_packet( - DNS_PACKET_ID(q->request_dns_packet), - q->answer_rcode, - q->question_idna, - q->answer, - !!q->request_dns_packet->opt, - DNS_PACKET_DO(q->request_dns_packet), - DNS_PACKET_DO(q->request_dns_packet) && q->answer_authenticated, - &reply); - if (r < 0) { - log_debug_errno(r, "Failed to build reply packet: %m"); - break; - } - - (void) dns_stub_send(q->manager, q->request_dns_stream, q->request_dns_packet, reply); - break; - } - - case DNS_TRANSACTION_RCODE_FAILURE: - (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, q->answer_rcode); - break; - - case DNS_TRANSACTION_NOT_FOUND: - (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_NXDOMAIN); - break; - - case DNS_TRANSACTION_TIMEOUT: - case DNS_TRANSACTION_ATTEMPTS_MAX_REACHED: - /* Propagate a timeout as a no packet, i.e. that the client also gets a timeout */ - break; - - case DNS_TRANSACTION_NO_SERVERS: - case DNS_TRANSACTION_INVALID_REPLY: - case DNS_TRANSACTION_ERRNO: - case DNS_TRANSACTION_ABORTED: - case DNS_TRANSACTION_DNSSEC_FAILED: - case DNS_TRANSACTION_NO_TRUST_ANCHOR: - case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED: - case DNS_TRANSACTION_NETWORK_DOWN: - (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_SERVFAIL); - break; - - case DNS_TRANSACTION_NULL: - case DNS_TRANSACTION_PENDING: - case DNS_TRANSACTION_VALIDATING: - default: - assert_not_reached("Impossible state"); - } - - /* If there's a packet to write set, let's leave the stream around */ - if (q->request_dns_stream && DNS_STREAM_QUEUED(q->request_dns_stream)) { - - /* Detach the stream from our query (make it an orphan), but do not drop the reference to it. The - * default completion action of the stream will drop the reference. */ - - dns_stub_detach_stream(q->request_dns_stream); - q->request_dns_stream = NULL; - } - - dns_query_free(q); -} - -static int dns_stub_stream_complete(DnsStream *s, int error) { - assert(s); - - log_debug_errno(error, "DNS TCP connection terminated, destroying query: %m"); - - assert(s->query); - dns_query_free(s->query); - - return 0; -} - -static void dns_stub_process_query(Manager *m, DnsStream *s, DnsPacket *p) { - DnsQuery *q = NULL; - int r; - - assert(m); - assert(p); - assert(p->protocol == DNS_PROTOCOL_DNS); - - /* Takes ownership of the *s stream object */ - - if (in_addr_is_localhost(p->family, &p->sender) <= 0 || - in_addr_is_localhost(p->family, &p->destination) <= 0) { - log_error("Got packet on unexpected IP range, refusing."); - dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL); - goto fail; - } - - r = dns_packet_extract(p); - if (r < 0) { - log_debug_errno(r, "Failed to extract resources from incoming packet, ignoring packet: %m"); - dns_stub_send_failure(m, s, p, DNS_RCODE_FORMERR); - goto fail; - } - - if (!DNS_PACKET_VERSION_SUPPORTED(p)) { - log_debug("Got EDNS OPT field with unsupported version number."); - dns_stub_send_failure(m, s, p, DNS_RCODE_BADVERS); - goto fail; - } - - if (dns_type_is_obsolete(p->question->keys[0]->type)) { - log_debug("Got message with obsolete key type, refusing."); - dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP); - goto fail; - } - - if (dns_type_is_zone_transer(p->question->keys[0]->type)) { - log_debug("Got request for zone transfer, refusing."); - dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP); - goto fail; - } - - if (!DNS_PACKET_RD(p)) { - /* If the "rd" bit is off (i.e. recursion was not requested), then refuse operation */ - log_debug("Got request with recursion disabled, refusing."); - dns_stub_send_failure(m, s, p, DNS_RCODE_REFUSED); - goto fail; - } - - if (DNS_PACKET_DO(p) && DNS_PACKET_CD(p)) { - log_debug("Got request with DNSSEC CD bit set, refusing."); - dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP); - goto fail; - } - - r = dns_query_new(m, &q, p->question, p->question, 0, SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_SEARCH|SD_RESOLVED_NO_CNAME); - if (r < 0) { - log_error_errno(r, "Failed to generate query object: %m"); - dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL); - goto fail; - } - - /* Request that the TTL is corrected by the cached time for this lookup, so that we return vaguely useful TTLs */ - q->clamp_ttl = true; - - q->request_dns_packet = dns_packet_ref(p); - q->request_dns_stream = dns_stream_ref(s); /* make sure the stream stays around until we can send a reply through it */ - q->complete = dns_stub_query_complete; - - if (s) { - s->on_packet = NULL; - s->complete = dns_stub_stream_complete; - s->query = q; - } - - r = dns_query_go(q); - if (r < 0) { - log_error_errno(r, "Failed to start query: %m"); - dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL); - goto fail; - } - - log_info("Processing query..."); - return; - -fail: - if (s && DNS_STREAM_QUEUED(s)) - dns_stub_detach_stream(s); - - dns_query_free(q); -} - -static int on_dns_stub_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - Manager *m = userdata; - int r; - - r = manager_recv(m, fd, DNS_PROTOCOL_DNS, &p); - if (r <= 0) - return r; - - if (dns_packet_validate_query(p) > 0) { - log_debug("Got DNS stub UDP query packet for id %u", DNS_PACKET_ID(p)); - - dns_stub_process_query(m, NULL, p); - } else - log_debug("Invalid DNS stub UDP packet, ignoring."); - - return 0; -} - -static int manager_dns_stub_udp_fd(Manager *m) { - static const int one = 1; - union sockaddr_union sa = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(53), - .in.sin_addr.s_addr = htobe32(INADDR_DNS_STUB), - }; - _cleanup_close_ int fd = -1; - int r; - - if (m->dns_stub_udp_fd >= 0) - return m->dns_stub_udp_fd; - - fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (fd < 0) - return -errno; - - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one) < 0) - return -errno; - - if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof one) < 0) - return -errno; - - if (setsockopt(fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof one) < 0) - return -errno; - - /* Make sure no traffic from outside the local host can leak to onto this socket */ - if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, "lo", 3) < 0) - return -errno; - - if (bind(fd, &sa.sa, sizeof(sa.in)) < 0) - return -errno; - - r = sd_event_add_io(m->event, &m->dns_stub_udp_event_source, fd, EPOLLIN, on_dns_stub_packet, m); - if (r < 0) - return r; - - (void) sd_event_source_set_description(m->dns_stub_udp_event_source, "dns-stub-udp"); - m->dns_stub_udp_fd = fd; - fd = -1; - - return m->dns_stub_udp_fd; -} - -static int on_dns_stub_stream_packet(DnsStream *s) { - assert(s); - assert(s->read_packet); - - if (dns_packet_validate_query(s->read_packet) > 0) { - log_debug("Got DNS stub TCP query packet for id %u", DNS_PACKET_ID(s->read_packet)); - - dns_stub_process_query(s->manager, s, s->read_packet); - } else - log_debug("Invalid DNS stub TCP packet, ignoring."); - - /* Drop the reference to the stream. Either a query was created and added its own reference to the stream now, - * or that didn't happen in which case we want to free the stream */ - dns_stream_unref(s); - - return 0; -} - -static int on_dns_stub_stream(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - DnsStream *stream; - Manager *m = userdata; - int cfd, r; - - cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); - if (cfd < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return -errno; - } - - r = dns_stream_new(m, &stream, DNS_PROTOCOL_DNS, cfd); - if (r < 0) { - safe_close(cfd); - return r; - } - - stream->on_packet = on_dns_stub_stream_packet; - - /* We let the reference to the stream dangling here, it will either be dropped by the default "complete" action - * of the stream, or by our packet callback, or when the manager is shut down. */ - - return 0; -} - -static int manager_dns_stub_tcp_fd(Manager *m) { - static const int one = 1; - union sockaddr_union sa = { - .in.sin_family = AF_INET, - .in.sin_addr.s_addr = htobe32(INADDR_DNS_STUB), - .in.sin_port = htobe16(53), - }; - _cleanup_close_ int fd = -1; - int r; - - if (m->dns_stub_tcp_fd >= 0) - return m->dns_stub_tcp_fd; - - fd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (fd < 0) - return -errno; - - if (setsockopt(fd, IPPROTO_IP, IP_TTL, &one, sizeof one) < 0) - return -errno; - - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one) < 0) - return -errno; - - if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof one) < 0) - return -errno; - - if (setsockopt(fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof one) < 0) - return -errno; - - /* Make sure no traffic from outside the local host can leak to onto this socket */ - if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, "lo", 3) < 0) - return -errno; - - if (bind(fd, &sa.sa, sizeof(sa.in)) < 0) - return -errno; - - if (listen(fd, SOMAXCONN) < 0) - return -errno; - - r = sd_event_add_io(m->event, &m->dns_stub_tcp_event_source, fd, EPOLLIN, on_dns_stub_stream, m); - if (r < 0) - return r; - - (void) sd_event_source_set_description(m->dns_stub_tcp_event_source, "dns-stub-tcp"); - m->dns_stub_tcp_fd = fd; - fd = -1; - - return m->dns_stub_tcp_fd; -} - -int manager_dns_stub_start(Manager *m) { - const char *t = "UDP"; - int r = 0; - - assert(m); - - if (IN_SET(m->dns_stub_listener_mode, DNS_STUB_LISTENER_YES, DNS_STUB_LISTENER_UDP)) - r = manager_dns_stub_udp_fd(m); - - if (r >= 0 && - IN_SET(m->dns_stub_listener_mode, DNS_STUB_LISTENER_YES, DNS_STUB_LISTENER_TCP)) { - t = "TCP"; - r = manager_dns_stub_tcp_fd(m); - } - - if (IN_SET(r, -EADDRINUSE, -EPERM)) { - if (r == -EADDRINUSE) - log_warning_errno(r, - "Another process is already listening on %s socket 127.0.0.53:53.\n" - "Turning off local DNS stub support.", t); - else - log_warning_errno(r, - "Failed to listen on %s socket 127.0.0.53:53: %m.\n" - "Turning off local DNS stub support.", t); - manager_dns_stub_stop(m); - } else if (r < 0) - return log_error_errno(r, "Failed to listen on %s socket 127.0.0.53:53: %m", t); - - return 0; -} - -void manager_dns_stub_stop(Manager *m) { - assert(m); - - m->dns_stub_udp_event_source = sd_event_source_unref(m->dns_stub_udp_event_source); - m->dns_stub_tcp_event_source = sd_event_source_unref(m->dns_stub_tcp_event_source); - - m->dns_stub_udp_fd = safe_close(m->dns_stub_udp_fd); - m->dns_stub_tcp_fd = safe_close(m->dns_stub_tcp_fd); -} diff --git a/src/resolve/resolved-dns-stub.h b/src/resolve/resolved-dns-stub.h deleted file mode 100644 index 12b86f6753..0000000000 --- a/src/resolve/resolved-dns-stub.h +++ /dev/null @@ -1,28 +0,0 @@ -#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" - -/* 127.0.0.53 in native endian */ -#define INADDR_DNS_STUB ((in_addr_t) 0x7f000035U) - -void manager_dns_stub_stop(Manager *m); -int manager_dns_stub_start(Manager *m); diff --git a/src/resolve/resolved-dns-synthesize.c b/src/resolve/resolved-dns-synthesize.c deleted file mode 100644 index e3003411f7..0000000000 --- a/src/resolve/resolved-dns-synthesize.c +++ /dev/null @@ -1,413 +0,0 @@ -/*** - 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 deleted file mode 100644 index 5d829bb2e7..0000000000 --- a/src/resolve/resolved-dns-synthesize.h +++ /dev/null @@ -1,30 +0,0 @@ -#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 deleted file mode 100644 index 2fce44ec8b..0000000000 --- a/src/resolve/resolved-dns-transaction.c +++ /dev/null @@ -1,3106 +0,0 @@ -/*** - 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 <sd-messages.h> - -#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" -#include "resolved-dns-transaction.h" -#include "resolved-llmnr.h" -#include "string-table.h" - -#define TRANSACTIONS_MAX 4096 - -static void dns_transaction_reset_answer(DnsTransaction *t) { - assert(t); - - t->received = dns_packet_unref(t->received); - t->answer = dns_answer_unref(t->answer); - t->answer_rcode = 0; - t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; - t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; - t->answer_authenticated = false; - t->answer_nsec_ttl = (uint32_t) -1; - t->answer_errno = 0; -} - -static void dns_transaction_flush_dnssec_transactions(DnsTransaction *t) { - DnsTransaction *z; - - assert(t); - - while ((z = set_steal_first(t->dnssec_transactions))) { - set_remove(z->notify_transactions, t); - set_remove(z->notify_transactions_done, t); - dns_transaction_gc(z); - } -} - -static void dns_transaction_close_connection(DnsTransaction *t) { - assert(t); - - if (t->stream) { - /* Let's detach the stream from our transaction, in case something else keeps a reference to it. */ - t->stream->complete = NULL; - t->stream->on_packet = NULL; - t->stream->transaction = NULL; - t->stream = dns_stream_unref(t->stream); - } - - t->dns_udp_event_source = sd_event_source_unref(t->dns_udp_event_source); - t->dns_udp_fd = safe_close(t->dns_udp_fd); -} - -static void dns_transaction_stop_timeout(DnsTransaction *t) { - assert(t); - - t->timeout_event_source = sd_event_source_unref(t->timeout_event_source); -} - -DnsTransaction* dns_transaction_free(DnsTransaction *t) { - DnsQueryCandidate *c; - DnsZoneItem *i; - DnsTransaction *z; - - if (!t) - return NULL; - - log_debug("Freeing transaction %" PRIu16 ".", t->id); - - dns_transaction_close_connection(t); - dns_transaction_stop_timeout(t); - - dns_packet_unref(t->sent); - dns_transaction_reset_answer(t); - - dns_server_unref(t->server); - - if (t->scope) { - hashmap_remove_value(t->scope->transactions_by_key, t->key, t); - LIST_REMOVE(transactions_by_scope, t->scope->transactions, t); - - if (t->id != 0) - hashmap_remove(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id)); - } - - while ((c = set_steal_first(t->notify_query_candidates))) - set_remove(c->transactions, t); - set_free(t->notify_query_candidates); - - while ((c = set_steal_first(t->notify_query_candidates_done))) - set_remove(c->transactions, t); - set_free(t->notify_query_candidates_done); - - while ((i = set_steal_first(t->notify_zone_items))) - i->probe_transaction = NULL; - set_free(t->notify_zone_items); - - while ((i = set_steal_first(t->notify_zone_items_done))) - i->probe_transaction = NULL; - set_free(t->notify_zone_items_done); - - while ((z = set_steal_first(t->notify_transactions))) - set_remove(z->dnssec_transactions, t); - set_free(t->notify_transactions); - - while ((z = set_steal_first(t->notify_transactions_done))) - set_remove(z->dnssec_transactions, t); - set_free(t->notify_transactions_done); - - dns_transaction_flush_dnssec_transactions(t); - set_free(t->dnssec_transactions); - - dns_answer_unref(t->validated_keys); - dns_resource_key_unref(t->key); - - return mfree(t); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsTransaction*, dns_transaction_free); - -bool dns_transaction_gc(DnsTransaction *t) { - assert(t); - - if (t->block_gc > 0) - return true; - - if (set_isempty(t->notify_query_candidates) && - set_isempty(t->notify_query_candidates_done) && - set_isempty(t->notify_zone_items) && - set_isempty(t->notify_zone_items_done) && - set_isempty(t->notify_transactions) && - set_isempty(t->notify_transactions_done)) { - dns_transaction_free(t); - return false; - } - - return true; -} - -static uint16_t pick_new_id(Manager *m) { - uint16_t new_id; - - /* Find a fresh, unused transaction id. Note that this loop is bounded because there's a limit on the number of - * transactions, and it's much lower than the space of IDs. */ - - assert_cc(TRANSACTIONS_MAX < 0xFFFF); - - do - random_bytes(&new_id, sizeof(new_id)); - while (new_id == 0 || - hashmap_get(m->dns_transactions, UINT_TO_PTR(new_id))); - - return new_id; -} - -int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) { - _cleanup_(dns_transaction_freep) DnsTransaction *t = NULL; - int r; - - assert(ret); - assert(s); - assert(key); - - /* Don't allow looking up invalid or pseudo RRs */ - if (!dns_type_is_valid_query(key->type)) - return -EINVAL; - if (dns_type_is_obsolete(key->type)) - return -EOPNOTSUPP; - - /* We only support the IN class */ - if (key->class != DNS_CLASS_IN && key->class != DNS_CLASS_ANY) - return -EOPNOTSUPP; - - if (hashmap_size(s->manager->dns_transactions) >= TRANSACTIONS_MAX) - return -EBUSY; - - r = hashmap_ensure_allocated(&s->manager->dns_transactions, NULL); - if (r < 0) - return r; - - r = hashmap_ensure_allocated(&s->transactions_by_key, &dns_resource_key_hash_ops); - if (r < 0) - return r; - - t = new0(DnsTransaction, 1); - if (!t) - return -ENOMEM; - - t->dns_udp_fd = -1; - t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; - t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; - t->answer_nsec_ttl = (uint32_t) -1; - t->key = dns_resource_key_ref(key); - t->current_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID; - t->clamp_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID; - - t->id = pick_new_id(s->manager); - - r = hashmap_put(s->manager->dns_transactions, UINT_TO_PTR(t->id), t); - if (r < 0) { - t->id = 0; - return r; - } - - r = hashmap_replace(s->transactions_by_key, t->key, t); - if (r < 0) { - hashmap_remove(s->manager->dns_transactions, UINT_TO_PTR(t->id)); - return r; - } - - LIST_PREPEND(transactions_by_scope, s->transactions, t); - t->scope = s; - - s->manager->n_transactions_total++; - - if (ret) - *ret = t; - - t = NULL; - - return 0; -} - -static void dns_transaction_shuffle_id(DnsTransaction *t) { - uint16_t new_id; - assert(t); - - /* Pick a new ID for this transaction. */ - - new_id = pick_new_id(t->scope->manager); - assert_se(hashmap_remove_and_put(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id), UINT_TO_PTR(new_id), t) >= 0); - - log_debug("Transaction %" PRIu16 " is now %" PRIu16 ".", t->id, new_id); - t->id = new_id; - - /* Make sure we generate a new packet with the new ID */ - t->sent = dns_packet_unref(t->sent); -} - -static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) { - _cleanup_free_ char *pretty = NULL; - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - DnsZoneItem *z; - - assert(t); - assert(p); - - if (manager_our_packet(t->scope->manager, p) != 0) - return; - - (void) in_addr_to_string(p->family, &p->sender, &pretty); - - log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s got tentative packet from %s.", - t->id, - dns_resource_key_to_string(t->key, key_str, sizeof key_str), - dns_protocol_to_string(t->scope->protocol), - t->scope->link ? t->scope->link->name : "*", - af_to_name_short(t->scope->family), - strnull(pretty)); - - /* RFC 4795, Section 4.1 says that the peer with the - * lexicographically smaller IP address loses */ - if (memcmp(&p->sender, &p->destination, FAMILY_ADDRESS_SIZE(p->family)) >= 0) { - log_debug("Peer has lexicographically larger IP address and thus lost in the conflict."); - return; - } - - log_debug("We have the lexicographically larger IP address and thus lost in the conflict."); - - t->block_gc++; - - while ((z = set_first(t->notify_zone_items))) { - /* First, make sure the zone item drops the reference - * to us */ - dns_zone_item_probe_stop(z); - - /* Secondly, report this as conflict, so that we might - * look for a different hostname */ - dns_zone_item_conflict(z); - } - t->block_gc--; - - dns_transaction_gc(t); -} - -void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { - DnsQueryCandidate *c; - DnsZoneItem *z; - DnsTransaction *d; - const char *st; - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - - assert(t); - assert(!DNS_TRANSACTION_IS_LIVE(state)); - - if (state == DNS_TRANSACTION_DNSSEC_FAILED) { - dns_resource_key_to_string(t->key, key_str, sizeof key_str); - - log_struct(LOG_NOTICE, - LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_FAILURE), - LOG_MESSAGE("DNSSEC validation failed for question %s: %s", key_str, dnssec_result_to_string(t->answer_dnssec_result)), - "DNS_TRANSACTION=%" PRIu16, t->id, - "DNS_QUESTION=%s", key_str, - "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_resource_key_to_string(t->key, key_str, sizeof key_str), - dns_protocol_to_string(t->scope->protocol), - t->scope->link ? t->scope->link->name : "*", - af_to_name_short(t->scope->family), - st, - t->answer_source < 0 ? "none" : dns_transaction_source_to_string(t->answer_source), - t->answer_authenticated ? "authenticated" : "unsigned"); - - t->state = state; - - dns_transaction_close_connection(t); - dns_transaction_stop_timeout(t); - - /* Notify all queries that are interested, but make sure the - * transaction isn't freed while we are still looking at it */ - t->block_gc++; - - SET_FOREACH_MOVE(c, t->notify_query_candidates_done, t->notify_query_candidates) - dns_query_candidate_notify(c); - SWAP_TWO(t->notify_query_candidates, t->notify_query_candidates_done); - - SET_FOREACH_MOVE(z, t->notify_zone_items_done, t->notify_zone_items) - dns_zone_item_notify(z); - SWAP_TWO(t->notify_zone_items, t->notify_zone_items_done); - - SET_FOREACH_MOVE(d, t->notify_transactions_done, t->notify_transactions) - dns_transaction_notify(d, t); - SWAP_TWO(t->notify_transactions, t->notify_transactions_done); - - t->block_gc--; - dns_transaction_gc(t); -} - -static int dns_transaction_pick_server(DnsTransaction *t) { - DnsServer *server; - - assert(t); - assert(t->scope->protocol == DNS_PROTOCOL_DNS); - - /* Pick a DNS server and a feature level for it. */ - - server = dns_scope_get_dns_server(t->scope); - if (!server) - return -ESRCH; - - /* If we changed the server invalidate the feature level clamping, as the new server might have completely - * different properties. */ - if (server != t->server) - t->clamp_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID; - - t->current_feature_level = dns_server_possible_feature_level(server); - - /* Clamp the feature level if that is requested. */ - if (t->clamp_feature_level != _DNS_SERVER_FEATURE_LEVEL_INVALID && - t->current_feature_level > t->clamp_feature_level) - t->current_feature_level = t->clamp_feature_level; - - log_debug("Using feature level %s for transaction %u.", dns_server_feature_level_to_string(t->current_feature_level), t->id); - - if (server == t->server) - return 0; - - dns_server_unref(t->server); - t->server = dns_server_ref(server); - - log_debug("Using DNS server %s for transaction %u.", dns_server_string(t->server), t->id); - - return 1; -} - -static void dns_transaction_retry(DnsTransaction *t, bool next_server) { - int r; - - assert(t); - - log_debug("Retrying transaction %" PRIu16 ".", t->id); - - /* Before we try again, switch to a new server. */ - if (next_server) - dns_scope_next_dns_server(t->scope); - - r = dns_transaction_go(t); - if (r < 0) { - t->answer_errno = -r; - dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); - } -} - -static int dns_transaction_maybe_restart(DnsTransaction *t) { - int r; - - assert(t); - - /* Returns > 0 if the transaction was restarted, 0 if not */ - - if (!t->server) - return 0; - - if (t->current_feature_level <= dns_server_possible_feature_level(t->server)) - return 0; - - /* The server's current feature level is lower than when we sent the original query. We learnt something from - the response or possibly an auxiliary DNSSEC response that we didn't know before. We take that as reason to - restart the whole transaction. This is a good idea to deal with servers that respond rubbish if we include - OPT RR or DO bit. One of these cases is documented here, for example: - https://open.nlnetlabs.nl/pipermail/dnssec-trigger/2014-November/000376.html */ - - log_debug("Server feature level is now lower than when we began our transaction. Restarting with new ID."); - dns_transaction_shuffle_id(t); - - r = dns_transaction_go(t); - if (r < 0) - return r; - - return 1; -} - -static int on_stream_complete(DnsStream *s, int error) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - DnsTransaction *t; - - assert(s); - assert(s->transaction); - - /* Copy the data we care about out of the stream before we - * destroy it. */ - t = s->transaction; - p = dns_packet_ref(s->read_packet); - - dns_transaction_close_connection(t); - - 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); - - dns_transaction_retry(t, true); - return 0; - } - if (error != 0) { - t->answer_errno = error; - dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); - return 0; - } - - if (dns_packet_validate_reply(p) <= 0) { - log_debug("Invalid TCP reply packet."); - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return 0; - } - - dns_scope_check_conflicts(t->scope, p); - - t->block_gc++; - dns_transaction_process_reply(t, p); - t->block_gc--; - - /* If the response wasn't useful, then complete the transition - * now. After all, we are the worst feature set now with TCP - * sockets, and there's really no point in retrying. */ - if (t->state == DNS_TRANSACTION_PENDING) - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - else - dns_transaction_gc(t); - - return 0; -} - -static int dns_transaction_open_tcp(DnsTransaction *t) { - _cleanup_close_ int fd = -1; - int r; - - assert(t); - - dns_transaction_close_connection(t); - - switch (t->scope->protocol) { - - case DNS_PROTOCOL_DNS: - r = dns_transaction_pick_server(t); - if (r < 0) - return r; - - if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type)) - return -EOPNOTSUPP; - - r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level); - if (r < 0) - return r; - - fd = dns_scope_socket_tcp(t->scope, AF_UNSPEC, NULL, t->server, 53); - break; - - case DNS_PROTOCOL_LLMNR: - /* When we already received a reply to this (but it was truncated), send to its sender address */ - if (t->received) - fd = dns_scope_socket_tcp(t->scope, t->received->family, &t->received->sender, NULL, t->received->sender_port); - else { - union in_addr_union address; - int family = AF_UNSPEC; - - /* Otherwise, try to talk to the owner of a - * the IP address, in case this is a reverse - * PTR lookup */ - - r = dns_name_address(dns_resource_key_name(t->key), &family, &address); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - if (family != t->scope->family) - return -ESRCH; - - fd = dns_scope_socket_tcp(t->scope, family, &address, NULL, LLMNR_PORT); - } - - break; - - default: - return -EAFNOSUPPORT; - } - - if (fd < 0) - return fd; - - r = dns_stream_new(t->scope->manager, &t->stream, t->scope->protocol, fd); - if (r < 0) - return r; - fd = -1; - - r = dns_stream_write_packet(t->stream, t->sent); - if (r < 0) { - t->stream = dns_stream_unref(t->stream); - return r; - } - - t->stream->complete = on_stream_complete; - t->stream->transaction = t; - - /* The interface index is difficult to determine if we are - * connecting to the local host, hence fill this in right away - * instead of determining it from the socket */ - t->stream->ifindex = dns_scope_ifindex(t->scope); - - dns_transaction_reset_answer(t); - - t->tried_stream = true; - - return 0; -} - -static void dns_transaction_cache_answer(DnsTransaction *t) { - assert(t); - - /* For mDNS we cache whenever we get the packet, rather than - * in each transaction. */ - if (!IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR)) - return; - - /* Caching disabled? */ - if (!t->scope->manager->enable_cache) - return; - - /* We never cache if this packet is from the local host, under - * the assumption that a locally running DNS server would - * cache this anyway, and probably knows better when to flush - * the cache then we could. */ - if (!DNS_PACKET_SHALL_CACHE(t->received)) - return; - - dns_cache_put(&t->scope->cache, - t->key, - t->answer_rcode, - t->answer, - t->answer_authenticated, - t->answer_nsec_ttl, - 0, - t->received->family, - &t->received->sender); -} - -static bool dns_transaction_dnssec_is_live(DnsTransaction *t) { - DnsTransaction *dt; - Iterator i; - - assert(t); - - SET_FOREACH(dt, t->dnssec_transactions, i) - if (DNS_TRANSACTION_IS_LIVE(dt->state)) - return true; - - return false; -} - -static int dns_transaction_dnssec_ready(DnsTransaction *t) { - DnsTransaction *dt; - Iterator i; - - assert(t); - - /* Checks whether the auxiliary DNSSEC transactions of our transaction have completed, or are still - * ongoing. Returns 0, if we aren't ready for the DNSSEC validation, positive if we are. */ - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - switch (dt->state) { - - case DNS_TRANSACTION_NULL: - case DNS_TRANSACTION_PENDING: - case DNS_TRANSACTION_VALIDATING: - /* Still ongoing */ - return 0; - - case DNS_TRANSACTION_RCODE_FAILURE: - if (!IN_SET(dt->answer_rcode, DNS_RCODE_NXDOMAIN, DNS_RCODE_SERVFAIL)) { - log_debug("Auxiliary DNSSEC RR query failed with rcode=%s.", dns_rcode_to_string(dt->answer_rcode)); - goto fail; - } - - /* Fall-through: NXDOMAIN/SERVFAIL is good enough for us. This is because some DNS servers - * erronously return NXDOMAIN/SERVFAIL for empty non-terminals (Akamai...) or missing DS - * records (Facebook), and we need to handle that nicely, when asking for parent SOA or similar - * RRs to make unsigned proofs. */ - - case DNS_TRANSACTION_SUCCESS: - /* All good. */ - break; - - case DNS_TRANSACTION_DNSSEC_FAILED: - /* We handle DNSSEC failures different from other errors, as we care about the DNSSEC - * validationr result */ - - log_debug("Auxiliary DNSSEC RR query failed validation: %s", dnssec_result_to_string(dt->answer_dnssec_result)); - t->answer_dnssec_result = dt->answer_dnssec_result; /* Copy error code over */ - dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); - return 0; - - - default: - log_debug("Auxiliary DNSSEC RR query failed with %s", dns_transaction_state_to_string(dt->state)); - goto fail; - } - } - - /* All is ready, we can go and validate */ - return 1; - -fail: - t->answer_dnssec_result = DNSSEC_FAILED_AUXILIARY; - dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); - return 0; -} - -static void dns_transaction_process_dnssec(DnsTransaction *t) { - int r; - - assert(t); - - /* Are there ongoing DNSSEC transactions? If so, let's wait for them. */ - r = dns_transaction_dnssec_ready(t); - 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) - 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 == -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) { - /* We are not in automatic downgrade mode, and the - * server is bad, refuse operation. */ - dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); - return; - } - - if (!IN_SET(t->answer_dnssec_result, - _DNSSEC_RESULT_INVALID, /* No DNSSEC validation enabled */ - DNSSEC_VALIDATED, /* Answer is signed and validated successfully */ - DNSSEC_UNSIGNED, /* Answer is right-fully unsigned */ - DNSSEC_INCOMPATIBLE_SERVER)) { /* Server does not do DNSSEC (Yay, we are downgrade attack vulnerable!) */ - dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); - return; - } - - 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); -} - -static int dns_transaction_has_positive_answer(DnsTransaction *t, DnsAnswerFlags *flags) { - int r; - - assert(t); - - /* Checks whether the answer is positive, i.e. either a direct - * answer to the question, or a CNAME/DNAME for it */ - - r = dns_answer_match_key(t->answer, t->key, flags); - if (r != 0) - return r; - - r = dns_answer_find_cname_or_dname(t->answer, t->key, NULL, flags); - if (r != 0) - return r; - - return false; -} - -static int dns_transaction_fix_rcode(DnsTransaction *t) { - int r; - - assert(t); - - /* Fix up the RCODE to SUCCESS if we get at least one matching RR in a response. Note that this contradicts the - * DNS RFCs a bit. Specifically, RFC 6604 Section 3 clarifies that the RCODE shall say something about a - * CNAME/DNAME chain element coming after the last chain element contained in the message, and not the first - * one included. However, it also indicates that not all DNS servers implement this correctly. Moreover, when - * using DNSSEC we usually only can prove the first element of a CNAME/DNAME chain anyway, hence let's settle - * on always processing the RCODE as referring to the immediate look-up we do, i.e. the first element of a - * CNAME/DNAME chain. This way, we uniformly handle CNAME/DNAME chains, regardless if the DNS server - * incorrectly implements RCODE, whether DNSSEC is in use, or whether the DNS server only supplied us with an - * incomplete CNAME/DNAME chain. - * - * Or in other words: if we get at least one positive reply in a message we patch NXDOMAIN to become SUCCESS, - * and then rely on the CNAME chasing logic to figure out that there's actually a CNAME error with a new - * lookup. */ - - if (t->answer_rcode != DNS_RCODE_NXDOMAIN) - return 0; - - r = dns_transaction_has_positive_answer(t, NULL); - if (r <= 0) - return r; - - t->answer_rcode = DNS_RCODE_SUCCESS; - return 0; -} - -void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { - usec_t ts; - int r; - - assert(t); - assert(p); - assert(t->scope); - assert(t->scope->manager); - - if (t->state != DNS_TRANSACTION_PENDING) - return; - - /* Note that this call might invalidate the query. Callers - * should hence not attempt to access the query or transaction - * after calling this function. */ - - log_debug("Processing incoming packet on transaction %" PRIu16".", t->id); - - switch (t->scope->protocol) { - - case DNS_PROTOCOL_LLMNR: - /* For LLMNR we will not accept any packets from other interfaces */ - - if (p->ifindex != dns_scope_ifindex(t->scope)) - return; - - if (p->family != t->scope->family) - return; - - /* Tentative packets are not full responses but still - * useful for identifying uniqueness conflicts during - * probing. */ - if (DNS_PACKET_LLMNR_T(p)) { - dns_transaction_tentative(t, p); - return; - } - - break; - - case DNS_PROTOCOL_MDNS: - /* For mDNS we will not accept any packets from other interfaces */ - - if (p->ifindex != dns_scope_ifindex(t->scope)) - return; - - if (p->family != t->scope->family) - return; - - break; - - case DNS_PROTOCOL_DNS: - /* Note that we do not need to verify the - * addresses/port numbers of incoming traffic, as we - * invoked connect() on our UDP socket in which case - * the kernel already does the needed verification for - * us. */ - break; - - default: - assert_not_reached("Invalid DNS protocol."); - } - - if (t->received != p) { - dns_packet_unref(t->received); - t->received = dns_packet_ref(p); - } - - t->answer_source = DNS_TRANSACTION_NETWORK; - - if (p->ipproto == IPPROTO_TCP) { - if (DNS_PACKET_TC(p)) { - /* Truncated via TCP? Somebody must be fucking with us */ - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - - if (DNS_PACKET_ID(p) != t->id) { - /* Not the reply to our query? Somebody must be fucking with us */ - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - } - - assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); - - switch (t->scope->protocol) { - - case DNS_PROTOCOL_DNS: - assert(t->server); - - if (IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) { - - /* Request failed, immediately try again with reduced features */ - - if (t->current_feature_level <= DNS_SERVER_FEATURE_LEVEL_WORST) { - /* This was already at the lowest possible feature level? If so, we can't downgrade - * this transaction anymore, hence let's process the response, and accept the rcode. */ - log_debug("Server returned error: %s", dns_rcode_to_string(DNS_PACKET_RCODE(p))); - break; - } - - /* Reduce this feature level by one and try again. */ - t->clamp_feature_level = t->current_feature_level - 1; - - log_debug("Server returned error %s, retrying transaction with reduced feature level %s.", - dns_rcode_to_string(DNS_PACKET_RCODE(p)), - dns_server_feature_level_to_string(t->clamp_feature_level)); - - dns_transaction_retry(t, false /* use the same server */); - return; - } else if (DNS_PACKET_TC(p)) - dns_server_packet_truncated(t->server, t->current_feature_level); - - break; - - case DNS_PROTOCOL_LLMNR: - case DNS_PROTOCOL_MDNS: - dns_scope_packet_received(t->scope, ts - t->start_usec); - break; - - default: - assert_not_reached("Invalid DNS protocol."); - } - - if (DNS_PACKET_TC(p)) { - - /* Truncated packets for mDNS are not allowed. Give up immediately. */ - if (t->scope->protocol == DNS_PROTOCOL_MDNS) { - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - - log_debug("Reply truncated, retrying via TCP."); - - /* Response was truncated, let's try again with good old TCP */ - r = dns_transaction_open_tcp(t); - if (r == -ESRCH) { - /* No servers found? Damn! */ - dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); - return; - } - if (r == -EOPNOTSUPP) { - /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */ - dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED); - return; - } - if (r < 0) { - /* On LLMNR, if we cannot connect to the host, - * we immediately give up */ - 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, true); - } - - return; - } - - /* After the superficial checks, actually parse the message. */ - r = dns_packet_extract(p); - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - - if (t->server) { - /* Report that we successfully received a valid packet with a good rcode after we initially got a bad - * rcode and subsequently downgraded the protocol */ - - if (IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN) && - t->clamp_feature_level != _DNS_SERVER_FEATURE_LEVEL_INVALID) - dns_server_packet_rcode_downgrade(t->server, t->clamp_feature_level); - - /* Report that the OPT RR was missing */ - if (!p->opt) - dns_server_packet_bad_opt(t->server, t->current_feature_level); - - /* Report that we successfully received a packet */ - dns_server_packet_received(t->server, p->ipproto, t->current_feature_level, ts - t->start_usec, p->size); - } - - /* 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) - goto fail; - if (r > 0) /* Transaction got restarted... */ - return; - - if (IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR)) { - - /* Only consider responses with equivalent query section to the request */ - r = dns_packet_is_reply_for(p, t->key); - if (r < 0) - goto fail; - if (r == 0) { - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - - /* Install the answer as answer to the transaction */ - dns_answer_unref(t->answer); - t->answer = dns_answer_ref(p->answer); - t->answer_rcode = DNS_PACKET_RCODE(p); - t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; - t->answer_authenticated = false; - - r = dns_transaction_fix_rcode(t); - if (r < 0) - goto fail; - - /* Block GC while starting requests for additional DNSSEC RRs */ - t->block_gc++; - r = dns_transaction_request_dnssec_keys(t); - t->block_gc--; - - /* Maybe the transaction is ready for GC'ing now? If so, free it and return. */ - if (!dns_transaction_gc(t)) - return; - - /* Requesting additional keys might have resulted in - * this transaction to fail, since the auxiliary - * request failed for some reason. If so, we are not - * in pending state anymore, and we should exit - * quickly. */ - if (t->state != DNS_TRANSACTION_PENDING) - return; - if (r < 0) - goto fail; - if (r > 0) { - /* There are DNSSEC transactions pending now. Update the state accordingly. */ - t->state = DNS_TRANSACTION_VALIDATING; - dns_transaction_close_connection(t); - dns_transaction_stop_timeout(t); - return; - } - } - - dns_transaction_process_dnssec(t); - 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) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - DnsTransaction *t = userdata; - int r; - - assert(t); - assert(t->scope); - - r = manager_recv(t->scope->manager, fd, DNS_PROTOCOL_DNS, &p); - if (ERRNO_IS_DISCONNECT(-r)) { - usec_t usec; - - /* UDP connection failure get reported via ICMP and then are possible delivered to us on the next - * recvmsg(). Treat this like a lost packet. */ - - log_debug_errno(r, "Connection failure for DNS UDP packet: %m"); - assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &usec) >= 0); - dns_server_packet_lost(t->server, IPPROTO_UDP, t->current_feature_level, usec - t->start_usec); - - dns_transaction_retry(t, true); - return 0; - } - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); - t->answer_errno = -r; - return 0; - } - - r = dns_packet_validate_reply(p); - if (r < 0) { - log_debug_errno(r, "Received invalid DNS packet as response, ignoring: %m"); - return 0; - } - if (r == 0) { - log_debug("Received inappropriate DNS packet as response, ignoring."); - return 0; - } - - if (DNS_PACKET_ID(p) != t->id) { - log_debug("Received packet with incorrect transaction ID, ignoring."); - return 0; - } - - dns_transaction_process_reply(t, p); - return 0; -} - -static int dns_transaction_emit_udp(DnsTransaction *t) { - int r; - - assert(t); - - if (t->scope->protocol == DNS_PROTOCOL_DNS) { - - r = dns_transaction_pick_server(t); - if (r < 0) - return r; - - if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_UDP) - return -EAGAIN; - - if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type)) - return -EOPNOTSUPP; - - if (r > 0 || t->dns_udp_fd < 0) { /* Server changed, or no connection yet. */ - int fd; - - dns_transaction_close_connection(t); - - fd = dns_scope_socket_udp(t->scope, t->server, 53); - if (fd < 0) - return fd; - - r = sd_event_add_io(t->scope->manager->event, &t->dns_udp_event_source, fd, EPOLLIN, on_dns_packet, t); - if (r < 0) { - safe_close(fd); - return r; - } - - (void) sd_event_source_set_description(t->dns_udp_event_source, "dns-transaction-udp"); - t->dns_udp_fd = fd; - } - - r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level); - if (r < 0) - return r; - } else - dns_transaction_close_connection(t); - - r = dns_scope_emit_udp(t->scope, t->dns_udp_fd, t->sent); - if (r < 0) - return r; - - dns_transaction_reset_answer(t); - - return 0; -} - -static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdata) { - DnsTransaction *t = userdata; - - assert(s); - assert(t); - - if (!t->initial_jitter_scheduled || t->initial_jitter_elapsed) { - /* Timeout reached? Increase the timeout for the server used */ - switch (t->scope->protocol) { - - case DNS_PROTOCOL_DNS: - assert(t->server); - dns_server_packet_lost(t->server, t->stream ? IPPROTO_TCP : IPPROTO_UDP, t->current_feature_level, usec - t->start_usec); - break; - - case DNS_PROTOCOL_LLMNR: - case DNS_PROTOCOL_MDNS: - dns_scope_packet_lost(t->scope, usec - t->start_usec); - break; - - default: - assert_not_reached("Invalid DNS protocol."); - } - - if (t->initial_jitter_scheduled) - t->initial_jitter_elapsed = true; - } - - log_debug("Timeout reached on transaction %" PRIu16 ".", t->id); - - dns_transaction_retry(t, true); - return 0; -} - -static usec_t transaction_get_resend_timeout(DnsTransaction *t) { - assert(t); - assert(t->scope); - - switch (t->scope->protocol) { - - case DNS_PROTOCOL_DNS: - assert(t->server); - return t->server->resend_timeout; - - case DNS_PROTOCOL_MDNS: - assert(t->n_attempts > 0); - return (1 << (t->n_attempts - 1)) * USEC_PER_SEC; - - case DNS_PROTOCOL_LLMNR: - return t->scope->resend_timeout; - - default: - assert_not_reached("Invalid DNS protocol."); - } -} - -static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { - int r; - - assert(t); - - 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; - } - - if (t->scope->protocol == DNS_PROTOCOL_LLMNR && t->tried_stream) { - /* If we already tried via a stream, then we don't - * retry on LLMNR. See RFC 4795, Section 2.7. */ - dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); - return 0; - } - - t->n_attempts++; - t->start_usec = ts; - - dns_transaction_reset_answer(t); - dns_transaction_flush_dnssec_transactions(t); - - /* Check the trust anchor. Do so only on classic DNS, since DNSSEC does not apply otherwise. */ - if (t->scope->protocol == DNS_PROTOCOL_DNS) { - r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, t->key, &t->answer); - if (r < 0) - return r; - if (r > 0) { - t->answer_rcode = DNS_RCODE_SUCCESS; - t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR; - t->answer_authenticated = true; - dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); - return 0; - } - - if (dns_name_is_root(dns_resource_key_name(t->key)) && - t->key->type == DNS_TYPE_DS) { - - /* Hmm, this is a request for the root DS? A - * DS RR doesn't exist in the root zone, and - * if our trust anchor didn't know it either, - * this means we cannot do any DNSSEC logic - * anymore. */ - - if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) { - /* We are in downgrade mode. In this - * case, synthesize an unsigned empty - * response, so that the any lookup - * depending on this one can continue - * assuming there was no DS, and hence - * the root zone was unsigned. */ - - t->answer_rcode = DNS_RCODE_SUCCESS; - t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR; - t->answer_authenticated = false; - dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); - } else - /* If we are not in downgrade mode, - * then fail the lookup, because we - * cannot reasonably answer it. There - * might be DS RRs, but we don't know - * them, and the DNS server won't tell - * them to us (and even if it would, - * we couldn't validate and trust them. */ - dns_transaction_complete(t, DNS_TRANSACTION_NO_TRUST_ANCHOR); - - return 0; - } - } - - /* Check the zone, but only if this transaction is not used - * for probing or verifying a zone item. */ - if (set_isempty(t->notify_zone_items)) { - - r = dns_zone_lookup(&t->scope->zone, t->key, dns_scope_ifindex(t->scope), &t->answer, NULL, NULL); - if (r < 0) - return r; - if (r > 0) { - t->answer_rcode = DNS_RCODE_SUCCESS; - t->answer_source = DNS_TRANSACTION_ZONE; - t->answer_authenticated = true; - dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); - return 0; - } - } - - /* Check the cache, but only if this transaction is not used - * for probing or verifying a zone item. */ - if (set_isempty(t->notify_zone_items)) { - - /* Before trying the cache, let's make sure we figured out a - * server to use. Should this cause a change of server this - * might flush the cache. */ - dns_scope_get_dns_server(t->scope); - - /* Let's then prune all outdated entries */ - dns_cache_prune(&t->scope->cache); - - r = dns_cache_lookup(&t->scope->cache, t->key, t->clamp_ttl, &t->answer_rcode, &t->answer, &t->answer_authenticated); - if (r < 0) - return r; - if (r > 0) { - t->answer_source = DNS_TRANSACTION_CACHE; - if (t->answer_rcode == DNS_RCODE_SUCCESS) - dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); - else - dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE); - return 0; - } - } - - return 1; -} - -static int dns_transaction_make_packet_mdns(DnsTransaction *t) { - - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - bool add_known_answers = false; - DnsTransaction *other; - unsigned qdcount; - usec_t ts; - int r; - - assert(t); - assert(t->scope->protocol == DNS_PROTOCOL_MDNS); - - /* Discard any previously prepared packet, so we can start over and coalesce again */ - t->sent = dns_packet_unref(t->sent); - - r = dns_packet_new_query(&p, t->scope->protocol, 0, false); - if (r < 0) - return r; - - r = dns_packet_append_key(p, t->key, NULL); - if (r < 0) - return r; - - qdcount = 1; - - if (dns_key_is_shared(t->key)) - add_known_answers = true; - - /* - * For mDNS, we want to coalesce as many open queries in pending transactions into one single - * query packet on the wire as possible. To achieve that, we iterate through all pending transactions - * in our current scope, and see whether their timing contraints allow them to be sent. - */ - - assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); - - LIST_FOREACH(transactions_by_scope, other, t->scope->transactions) { - - /* Skip ourselves */ - if (other == t) - continue; - - if (other->state != DNS_TRANSACTION_PENDING) - continue; - - if (other->next_attempt_after > ts) - continue; - - if (qdcount >= UINT16_MAX) - break; - - r = dns_packet_append_key(p, other->key, NULL); - - /* - * If we can't stuff more questions into the packet, just give up. - * One of the 'other' transactions will fire later and take care of the rest. - */ - if (r == -EMSGSIZE) - break; - - if (r < 0) - return r; - - r = dns_transaction_prepare(other, ts); - if (r <= 0) - continue; - - ts += transaction_get_resend_timeout(other); - - r = sd_event_add_time( - other->scope->manager->event, - &other->timeout_event_source, - clock_boottime_or_monotonic(), - ts, 0, - on_transaction_timeout, other); - if (r < 0) - return r; - - (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); - - other->state = DNS_TRANSACTION_PENDING; - other->next_attempt_after = ts; - - qdcount++; - - if (dns_key_is_shared(other->key)) - add_known_answers = true; - } - - DNS_PACKET_HEADER(p)->qdcount = htobe16(qdcount); - - /* Append known answer section if we're asking for any shared record */ - if (add_known_answers) { - r = dns_cache_export_shared_to_packet(&t->scope->cache, p); - if (r < 0) - return r; - } - - t->sent = p; - p = NULL; - - return 0; -} - -static int dns_transaction_make_packet(DnsTransaction *t) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - int r; - - assert(t); - - if (t->scope->protocol == DNS_PROTOCOL_MDNS) - return dns_transaction_make_packet_mdns(t); - - if (t->sent) - return 0; - - r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode != DNSSEC_NO); - if (r < 0) - return r; - - r = dns_packet_append_key(p, t->key, NULL); - if (r < 0) - return r; - - DNS_PACKET_HEADER(p)->qdcount = htobe16(1); - DNS_PACKET_HEADER(p)->id = t->id; - - t->sent = p; - p = NULL; - - return 0; -} - -int dns_transaction_go(DnsTransaction *t) { - usec_t ts; - int r; - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - - assert(t); - - /* Returns > 0 if the transaction is now pending, returns 0 if could be processed immediately and has finished - * now. */ - - assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); - - r = dns_transaction_prepare(t, ts); - if (r <= 0) - return r; - - log_debug("Transaction %" PRIu16 " for <%s> scope %s on %s/%s.", - t->id, - dns_resource_key_to_string(t->key, key_str, sizeof key_str), - dns_protocol_to_string(t->scope->protocol), - t->scope->link ? t->scope->link->name : "*", - af_to_name_short(t->scope->family)); - - if (!t->initial_jitter_scheduled && - (t->scope->protocol == DNS_PROTOCOL_LLMNR || - t->scope->protocol == DNS_PROTOCOL_MDNS)) { - usec_t jitter, accuracy; - - /* RFC 4795 Section 2.7 suggests all queries should be - * delayed by a random time from 0 to JITTER_INTERVAL. */ - - t->initial_jitter_scheduled = true; - - random_bytes(&jitter, sizeof(jitter)); - - switch (t->scope->protocol) { - - case DNS_PROTOCOL_LLMNR: - jitter %= LLMNR_JITTER_INTERVAL_USEC; - accuracy = LLMNR_JITTER_INTERVAL_USEC; - break; - - case DNS_PROTOCOL_MDNS: - jitter %= MDNS_JITTER_RANGE_USEC; - jitter += MDNS_JITTER_MIN_USEC; - accuracy = MDNS_JITTER_RANGE_USEC; - break; - default: - assert_not_reached("bad protocol"); - } - - r = sd_event_add_time( - t->scope->manager->event, - &t->timeout_event_source, - clock_boottime_or_monotonic(), - ts + jitter, accuracy, - on_transaction_timeout, t); - if (r < 0) - return r; - - (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); - - t->n_attempts = 0; - t->next_attempt_after = ts; - t->state = DNS_TRANSACTION_PENDING; - - log_debug("Delaying %s transaction for " USEC_FMT "us.", dns_protocol_to_string(t->scope->protocol), jitter); - return 0; - } - - /* Otherwise, we need to ask the network */ - r = dns_transaction_make_packet(t); - if (r < 0) - return r; - - if (t->scope->protocol == DNS_PROTOCOL_LLMNR && - (dns_name_endswith(dns_resource_key_name(t->key), "in-addr.arpa") > 0 || - dns_name_endswith(dns_resource_key_name(t->key), "ip6.arpa") > 0)) { - - /* RFC 4795, Section 2.4. says reverse lookups shall - * always be made via TCP on LLMNR */ - r = dns_transaction_open_tcp(t); - } else { - /* Try via UDP, and if that fails due to large size or lack of - * support try via TCP */ - r = dns_transaction_emit_udp(t); - if (r == -EMSGSIZE) - log_debug("Sending query via TCP since it is too large."); - if (r == -EAGAIN) - log_debug("Sending query via TCP since server doesn't support UDP."); - if (r == -EMSGSIZE || r == -EAGAIN) - r = dns_transaction_open_tcp(t); - } - - if (r == -ESRCH) { - /* No servers to send this to? */ - dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); - return 0; - } - if (r == -EOPNOTSUPP) { - /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */ - dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED); - return 0; - } - if (t->scope->protocol == DNS_PROTOCOL_LLMNR && ERRNO_IS_DISCONNECT(-r)) { - /* On LLMNR, if we cannot connect to a host via TCP when doing reverse 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) - return r; - - /* Couldn't send? Try immediately again, with a new server */ - dns_scope_next_dns_server(t->scope); - - return dns_transaction_go(t); - } - - ts += transaction_get_resend_timeout(t); - - r = sd_event_add_time( - t->scope->manager->event, - &t->timeout_event_source, - clock_boottime_or_monotonic(), - ts, 0, - on_transaction_timeout, t); - if (r < 0) - return r; - - (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); - - t->state = DNS_TRANSACTION_PENDING; - t->next_attempt_after = ts; - - return 1; -} - -static int dns_transaction_find_cyclic(DnsTransaction *t, DnsTransaction *aux) { - DnsTransaction *n; - Iterator i; - int r; - - assert(t); - assert(aux); - - /* Try to find cyclic dependencies between transaction objects */ - - if (t == aux) - return 1; - - SET_FOREACH(n, aux->dnssec_transactions, i) { - r = dns_transaction_find_cyclic(t, n); - if (r != 0) - return r; - } - - return 0; -} - -static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResourceKey *key, DnsTransaction **ret) { - DnsTransaction *aux; - int r; - - assert(t); - assert(ret); - assert(key); - - aux = dns_scope_find_transaction(t->scope, key, true); - if (!aux) { - r = dns_transaction_new(&aux, t->scope, key); - if (r < 0) - return r; - } else { - if (set_contains(t->dnssec_transactions, aux)) { - *ret = aux; - return 0; - } - - r = dns_transaction_find_cyclic(t, aux); - if (r < 0) - return r; - if (r > 0) { - char s[DNS_RESOURCE_KEY_STRING_MAX], saux[DNS_RESOURCE_KEY_STRING_MAX]; - - log_debug("Potential cyclic dependency, refusing to add transaction %" PRIu16 " (%s) as dependency for %" PRIu16 " (%s).", - aux->id, - dns_resource_key_to_string(t->key, s, sizeof s), - t->id, - dns_resource_key_to_string(aux->key, saux, sizeof saux)); - - return -ELOOP; - } - } - - r = set_ensure_allocated(&t->dnssec_transactions, NULL); - if (r < 0) - goto gc; - - r = set_ensure_allocated(&aux->notify_transactions, NULL); - if (r < 0) - goto gc; - - r = set_ensure_allocated(&aux->notify_transactions_done, NULL); - if (r < 0) - goto gc; - - r = set_put(t->dnssec_transactions, aux); - if (r < 0) - goto gc; - - r = set_put(aux->notify_transactions, t); - if (r < 0) { - (void) set_remove(t->dnssec_transactions, aux); - goto gc; - } - - *ret = aux; - return 1; - -gc: - dns_transaction_gc(aux); - return r; -} - -static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey *key) { - _cleanup_(dns_answer_unrefp) DnsAnswer *a = NULL; - DnsTransaction *aux; - int r; - - assert(t); - assert(key); - - /* Try to get the data from the trust anchor */ - r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, key, &a); - if (r < 0) - return r; - if (r > 0) { - r = dns_answer_extend(&t->validated_keys, a); - if (r < 0) - return r; - - return 0; - } - - /* This didn't work, ask for it via the network/cache then. */ - r = dns_transaction_add_dnssec_transaction(t, key, &aux); - if (r == -ELOOP) /* This would result in a cyclic dependency */ - return 0; - if (r < 0) - return r; - - if (aux->state == DNS_TRANSACTION_NULL) { - r = dns_transaction_go(aux); - if (r < 0) - return r; - } - - return 1; -} - -static int dns_transaction_negative_trust_anchor_lookup(DnsTransaction *t, const char *name) { - int r; - - assert(t); - - /* Check whether the specified name is in the NTA - * database, either in the global one, or the link-local - * one. */ - - r = dns_trust_anchor_lookup_negative(&t->scope->manager->trust_anchor, name); - if (r != 0) - return r; - - if (!t->scope->link) - return 0; - - return set_contains(t->scope->link->dnssec_negative_trust_anchors, name); -} - -static int dns_transaction_has_unsigned_negative_answer(DnsTransaction *t) { - int r; - - assert(t); - - /* Checks whether the answer is negative, and lacks NSEC/NSEC3 - * RRs to prove it */ - - r = dns_transaction_has_positive_answer(t, NULL); - if (r < 0) - return r; - if (r > 0) - return false; - - /* Is this key explicitly listed as a negative trust anchor? - * If so, it's nothing we need to care about */ - r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(t->key)); - if (r < 0) - return r; - if (r > 0) - return false; - - /* The answer does not contain any RRs that match to the - * question. If so, let's see if there are any NSEC/NSEC3 RRs - * included. If not, the answer is unsigned. */ - - r = dns_answer_contains_nsec_or_nsec3(t->answer); - if (r < 0) - return r; - if (r > 0) - return false; - - return true; -} - -static int dns_transaction_is_primary_response(DnsTransaction *t, DnsResourceRecord *rr) { - int r; - - assert(t); - assert(rr); - - /* Check if the specified RR is the "primary" response, - * i.e. either matches the question precisely or is a - * CNAME/DNAME for it. */ - - r = dns_resource_key_match_rr(t->key, rr, NULL); - if (r != 0) - return r; - - return dns_resource_key_match_cname_or_dname(t->key, rr->key, NULL); -} - -static bool dns_transaction_dnssec_supported(DnsTransaction *t) { - assert(t); - - /* Checks whether our transaction's DNS server is assumed to be compatible with DNSSEC. Returns false as soon - * as we changed our mind about a server, and now believe it is incompatible with DNSSEC. */ - - if (t->scope->protocol != DNS_PROTOCOL_DNS) - return false; - - /* If we have picked no server, then we are working from the cache or some other source, and DNSSEC might well - * be supported, hence return true. */ - if (!t->server) - return true; - - /* Note that we do not check the feature level actually used for the transaction but instead the feature level - * the server is known to support currently, as the transaction feature level might be lower than what the - * server actually supports, since we might have downgraded this transaction's feature level because we got a - * SERVFAIL earlier and wanted to check whether downgrading fixes it. */ - - return dns_server_dnssec_supported(t->server); -} - -static bool dns_transaction_dnssec_supported_full(DnsTransaction *t) { - DnsTransaction *dt; - Iterator i; - - assert(t); - - /* Checks whether our transaction our any of the auxiliary transactions couldn't do DNSSEC. */ - - if (!dns_transaction_dnssec_supported(t)) - return false; - - SET_FOREACH(dt, t->dnssec_transactions, i) - if (!dns_transaction_dnssec_supported(dt)) - return false; - - return true; -} - -int dns_transaction_request_dnssec_keys(DnsTransaction *t) { - DnsResourceRecord *rr; - - int r; - - assert(t); - - /* - * Retrieve all auxiliary RRs for the answer we got, so that - * we can verify signatures or prove that RRs are rightfully - * unsigned. Specifically: - * - * - For RRSIG we get the matching DNSKEY - * - For DNSKEY we get the matching DS - * - For unsigned SOA/NS we get the matching DS - * - For unsigned CNAME/DNAME/DS we get the parent SOA RR - * - For other unsigned RRs we get the matching SOA RR - * - For SOA/NS queries with no matching response RR, and no NSEC/NSEC3, the DS RR - * - For DS queries with no matching response RRs, and no NSEC/NSEC3, the parent's SOA RR - * - For other queries with no matching response RRs, and no NSEC/NSEC3, the SOA RR - */ - - if (t->scope->dnssec_mode == DNSSEC_NO) - return 0; - if (t->answer_source != DNS_TRANSACTION_NETWORK) - return 0; /* We only need to validate stuff from the network */ - if (!dns_transaction_dnssec_supported(t)) - return 0; /* If we can't do DNSSEC anyway there's no point in geting the auxiliary RRs */ - - DNS_ANSWER_FOREACH(rr, t->answer) { - - if (dns_type_is_pseudo(rr->key->type)) - continue; - - /* If this RR is in the negative trust anchor, we don't need to validate it. */ - r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(rr->key)); - if (r < 0) - return r; - if (r > 0) - continue; - - switch (rr->key->type) { - - case DNS_TYPE_RRSIG: { - /* For each RRSIG we request the matching DNSKEY */ - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *dnskey = NULL; - - /* If this RRSIG is about a DNSKEY RR and the - * signer is the same as the owner, then we - * already have the DNSKEY, and we don't have - * to look for more. */ - if (rr->rrsig.type_covered == DNS_TYPE_DNSKEY) { - r = dns_name_equal(rr->rrsig.signer, dns_resource_key_name(rr->key)); - if (r < 0) - return r; - if (r > 0) - continue; - } - - /* If the signer is not a parent of our - * original query, then this is about an - * auxiliary RRset, but not anything we asked - * for. In this case we aren't interested, - * because we don't want to request additional - * RRs for stuff we didn't really ask for, and - * also to avoid request loops, where - * additional RRs from one transaction result - * in another transaction whose additonal RRs - * point back to the original transaction, and - * we deadlock. */ - r = dns_name_endswith(dns_resource_key_name(t->key), rr->rrsig.signer); - if (r < 0) - return r; - if (r == 0) - continue; - - dnskey = dns_resource_key_new(rr->key->class, DNS_TYPE_DNSKEY, rr->rrsig.signer); - if (!dnskey) - return -ENOMEM; - - log_debug("Requesting DNSKEY to validate transaction %" PRIu16" (%s, RRSIG with key tag: %" PRIu16 ").", - t->id, dns_resource_key_name(rr->key), rr->rrsig.key_tag); - r = dns_transaction_request_dnssec_rr(t, dnskey); - if (r < 0) - return r; - break; - } - - case DNS_TYPE_DNSKEY: { - /* For each DNSKEY we request the matching DS */ - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL; - - /* If the DNSKEY we are looking at is not for - * zone we are interested in, nor any of its - * parents, we aren't interested, and don't - * request it. After all, we don't want to end - * up in request loops, and want to keep - * additional traffic down. */ - - r = dns_name_endswith(dns_resource_key_name(t->key), dns_resource_key_name(rr->key)); - if (r < 0) - return r; - if (r == 0) - continue; - - ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, dns_resource_key_name(rr->key)); - if (!ds) - return -ENOMEM; - - log_debug("Requesting DS to validate transaction %" PRIu16" (%s, DNSKEY with key tag: %" PRIu16 ").", - t->id, dns_resource_key_name(rr->key), dnssec_keytag(rr, false)); - r = dns_transaction_request_dnssec_rr(t, ds); - if (r < 0) - return r; - - break; - } - - case DNS_TYPE_SOA: - case DNS_TYPE_NS: { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL; - - /* For an unsigned SOA or NS, try to acquire - * the matching DS RR, as we are at a zone cut - * then, and whether a DS exists tells us - * whether the zone is signed. Do so only if - * this RR matches our original question, - * however. */ - - r = dns_resource_key_match_rr(t->key, rr, NULL); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dnssec_has_rrsig(t->answer, rr->key); - if (r < 0) - return r; - if (r > 0) - continue; - - ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, dns_resource_key_name(rr->key)); - if (!ds) - return -ENOMEM; - - log_debug("Requesting DS to validate transaction %" PRIu16 " (%s, unsigned SOA/NS RRset).", - t->id, dns_resource_key_name(rr->key)); - r = dns_transaction_request_dnssec_rr(t, ds); - if (r < 0) - return r; - - break; - } - - case DNS_TYPE_DS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; - const char *name; - - /* CNAMEs and DNAMEs cannot be located at a - * zone apex, hence ask for the parent SOA for - * unsigned CNAME/DNAME RRs, maybe that's the - * apex. But do all that only if this is - * actually a response to our original - * question. - * - * Similar for DS RRs, which are signed when - * the parent SOA is signed. */ - - r = dns_transaction_is_primary_response(t, rr); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dnssec_has_rrsig(t->answer, rr->key); - if (r < 0) - return r; - if (r > 0) - continue; - - r = dns_answer_has_dname_for_cname(t->answer, rr); - if (r < 0) - return r; - if (r > 0) - continue; - - name = dns_resource_key_name(rr->key); - r = dns_name_parent(&name); - if (r < 0) - return r; - if (r == 0) - continue; - - soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, name); - if (!soa) - return -ENOMEM; - - log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned CNAME/DNAME/DS RRset).", - t->id, dns_resource_key_name(rr->key)); - r = dns_transaction_request_dnssec_rr(t, soa); - if (r < 0) - return r; - - break; - } - - default: { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; - - /* For other unsigned RRsets (including - * NSEC/NSEC3!), look for proof the zone is - * unsigned, by requesting the SOA RR of the - * zone. However, do so only if they are - * directly relevant to our original - * question. */ - - r = dns_transaction_is_primary_response(t, rr); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dnssec_has_rrsig(t->answer, rr->key); - if (r < 0) - return r; - if (r > 0) - continue; - - soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, dns_resource_key_name(rr->key)); - if (!soa) - return -ENOMEM; - - log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned non-SOA/NS RRset <%s>).", - t->id, dns_resource_key_name(rr->key), dns_resource_record_to_string(rr)); - r = dns_transaction_request_dnssec_rr(t, soa); - if (r < 0) - return r; - break; - }} - } - - /* Above, we requested everything necessary to validate what - * we got. Now, let's request what we need to validate what we - * didn't get... */ - - r = dns_transaction_has_unsigned_negative_answer(t); - if (r < 0) - return r; - if (r > 0) { - const char *name; - uint16_t type = 0; - - name = dns_resource_key_name(t->key); - - /* If this was a SOA or NS request, then check if there's a DS RR for the same domain. Note that this - * could also be used as indication that we are not at a zone apex, but in real world setups there are - * too many broken DNS servers (Hello, incapdns.net!) where non-terminal zones return NXDOMAIN even - * though they have further children. If this was a DS request, then it's signed when the parent zone - * is signed, hence ask the parent SOA in that case. If this was any other RR then ask for the SOA RR, - * to see if that is signed. */ - - if (t->key->type == DNS_TYPE_DS) { - r = dns_name_parent(&name); - if (r > 0) { - type = DNS_TYPE_SOA; - log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned empty DS response).", - t->id, dns_resource_key_name(t->key)); - } else - name = NULL; - - } else if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS)) { - - type = DNS_TYPE_DS; - log_debug("Requesting DS to validate transaction %" PRIu16 " (%s, unsigned empty SOA/NS response).", - t->id, dns_resource_key_name(t->key)); - - } else { - type = DNS_TYPE_SOA; - log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned empty non-SOA/NS/DS response).", - t->id, dns_resource_key_name(t->key)); - } - - if (name) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; - - soa = dns_resource_key_new(t->key->class, type, name); - if (!soa) - return -ENOMEM; - - r = dns_transaction_request_dnssec_rr(t, soa); - if (r < 0) - return r; - } - } - - return dns_transaction_dnssec_is_live(t); -} - -void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source) { - assert(t); - assert(source); - - /* Invoked whenever any of our auxiliary DNSSEC transactions completed its work. If the state is still PENDING, - we are still in the loop that adds further DNSSEC transactions, hence don't check if we are ready yet. If - the state is VALIDATING however, we should check if we are complete now. */ - - if (t->state == DNS_TRANSACTION_VALIDATING) - dns_transaction_process_dnssec(t); -} - -static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) { - DnsResourceRecord *rr; - int ifindex, r; - - assert(t); - - /* Add all DNSKEY RRs from the answer that are validated by DS - * RRs from the list of validated keys to the list of - * validated keys. */ - - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, t->answer) { - - r = dnssec_verify_dnskey_by_ds_search(rr, t->validated_keys); - if (r < 0) - return r; - if (r == 0) - continue; - - /* If so, the DNSKEY is validated too. */ - r = dns_answer_add_extend(&t->validated_keys, rr, ifindex, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - return 0; -} - -static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *rr) { - int r; - - assert(t); - assert(rr); - - /* Checks if the RR we are looking for must be signed with an - * RRSIG. This is used for positive responses. */ - - if (t->scope->dnssec_mode == DNSSEC_NO) - return false; - - if (dns_type_is_pseudo(rr->key->type)) - return -EINVAL; - - r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(rr->key)); - if (r < 0) - return r; - if (r > 0) - return false; - - switch (rr->key->type) { - - case DNS_TYPE_RRSIG: - /* RRSIGs are the signatures themselves, they need no signing. */ - return false; - - case DNS_TYPE_SOA: - case DNS_TYPE_NS: { - DnsTransaction *dt; - Iterator i; - - /* For SOA or NS RRs we look for a matching DS transaction */ - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - if (dt->key->class != rr->key->class) - continue; - if (dt->key->type != DNS_TYPE_DS) - continue; - - r = dns_name_equal(dns_resource_key_name(dt->key), dns_resource_key_name(rr->key)); - if (r < 0) - return r; - if (r == 0) - continue; - - /* We found a DS transactions for the SOA/NS - * RRs we are looking at. If it discovered signed DS - * RRs, then we need to be signed, too. */ - - if (!dt->answer_authenticated) - return false; - - return dns_answer_match_key(dt->answer, dt->key, NULL); - } - - /* We found nothing that proves this is safe to leave - * this unauthenticated, hence ask inist on - * authentication. */ - return true; - } - - case DNS_TYPE_DS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: { - const char *parent = NULL; - DnsTransaction *dt; - Iterator i; - - /* - * CNAME/DNAME RRs cannot be located at a zone apex, hence look directly for the parent SOA. - * - * DS RRs are signed if the parent is signed, hence also look at the parent SOA - */ - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - if (dt->key->class != rr->key->class) - continue; - if (dt->key->type != DNS_TYPE_SOA) - continue; - - if (!parent) { - parent = dns_resource_key_name(rr->key); - r = dns_name_parent(&parent); - if (r < 0) - return r; - if (r == 0) { - if (rr->key->type == DNS_TYPE_DS) - return true; - - /* A CNAME/DNAME without a parent? That's sooo weird. */ - log_debug("Transaction %" PRIu16 " claims CNAME/DNAME at root. Refusing.", t->id); - return -EBADMSG; - } - } - - r = dns_name_equal(dns_resource_key_name(dt->key), parent); - if (r < 0) - return r; - if (r == 0) - continue; - - return t->answer_authenticated; - } - - return true; - } - - default: { - DnsTransaction *dt; - Iterator i; - - /* Any other kind of RR (including DNSKEY/NSEC/NSEC3). Let's see if our SOA lookup was authenticated */ - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - if (dt->key->class != rr->key->class) - continue; - if (dt->key->type != DNS_TYPE_SOA) - continue; - - r = dns_name_equal(dns_resource_key_name(dt->key), dns_resource_key_name(rr->key)); - if (r < 0) - return r; - if (r == 0) - continue; - - /* We found the transaction that was supposed to find - * the SOA RR for us. It was successful, but found no - * RR for us. This means we are not at a zone cut. In - * this case, we require authentication if the SOA - * lookup was authenticated too. */ - return t->answer_authenticated; - } - - return true; - }} -} - -static int dns_transaction_in_private_tld(DnsTransaction *t, const DnsResourceKey *key) { - DnsTransaction *dt; - const char *tld; - Iterator i; - int r; - - /* If DNSSEC downgrade mode is on, checks whether the - * specified RR is one level below a TLD we have proven not to - * exist. In such a case we assume that this is a private - * domain, and permit it. - * - * This detects cases like the Fritz!Box router networks. Each - * Fritz!Box router serves a private "fritz.box" zone, in the - * non-existing TLD "box". Requests for the "fritz.box" domain - * are served by the router itself, while requests for the - * "box" domain will result in NXDOMAIN. - * - * Note that this logic is unable to detect cases where a - * router serves a private DNS zone directly under - * non-existing TLD. In such a case we cannot detect whether - * the TLD is supposed to exist or not, as all requests we - * make for it will be answered by the router's zone, and not - * by the root zone. */ - - assert(t); - - if (t->scope->dnssec_mode != DNSSEC_ALLOW_DOWNGRADE) - return false; /* In strict DNSSEC mode what doesn't exist, doesn't exist */ - - tld = dns_resource_key_name(key); - r = dns_name_parent(&tld); - if (r < 0) - return r; - if (r == 0) - return false; /* Already the root domain */ - - if (!dns_name_is_single_label(tld)) - return false; - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - if (dt->key->class != key->class) - continue; - - r = dns_name_equal(dns_resource_key_name(dt->key), tld); - if (r < 0) - return r; - if (r == 0) - continue; - - /* We found an auxiliary lookup we did for the TLD. If - * that returned with NXDOMAIN, we know the TLD didn't - * exist, and hence this might be a private zone. */ - - return dt->answer_rcode == DNS_RCODE_NXDOMAIN; - } - - return false; -} - -static int dns_transaction_requires_nsec(DnsTransaction *t) { - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - DnsTransaction *dt; - const char *name; - uint16_t type = 0; - Iterator i; - int r; - - assert(t); - - /* Checks if we need to insist on NSEC/NSEC3 RRs for proving - * this negative reply */ - - if (t->scope->dnssec_mode == DNSSEC_NO) - return false; - - if (dns_type_is_pseudo(t->key->type)) - return -EINVAL; - - r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(t->key)); - if (r < 0) - return r; - if (r > 0) - return false; - - r = dns_transaction_in_private_tld(t, t->key); - if (r < 0) - return r; - if (r > 0) { - /* The lookup is from a TLD that is proven not to - * exist, and we are in downgrade mode, hence ignore - * that fact that we didn't get any NSEC RRs.*/ - - log_info("Detected a negative query %s in a private DNS zone, permitting unsigned response.", - dns_resource_key_to_string(t->key, key_str, sizeof key_str)); - return false; - } - - name = dns_resource_key_name(t->key); - - if (t->key->type == DNS_TYPE_DS) { - - /* We got a negative reply for this DS lookup? DS RRs are signed when their parent zone is signed, - * hence check the parent SOA in this case. */ - - r = dns_name_parent(&name); - if (r < 0) - return r; - if (r == 0) - return true; - - type = DNS_TYPE_SOA; - - } else if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS)) - /* We got a negative reply for this SOA/NS lookup? If so, check if there's a DS RR for this */ - type = DNS_TYPE_DS; - else - /* For all other negative replies, check for the SOA lookup */ - type = DNS_TYPE_SOA; - - /* For all other RRs we check the SOA on the same level to see - * if it's signed. */ - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - if (dt->key->class != t->key->class) - continue; - if (dt->key->type != type) - continue; - - r = dns_name_equal(dns_resource_key_name(dt->key), name); - if (r < 0) - return r; - if (r == 0) - continue; - - return dt->answer_authenticated; - } - - /* If in doubt, require NSEC/NSEC3 */ - return true; -} - -static int dns_transaction_dnskey_authenticated(DnsTransaction *t, DnsResourceRecord *rr) { - DnsResourceRecord *rrsig; - bool found = false; - int r; - - /* Checks whether any of the DNSKEYs used for the RRSIGs for - * the specified RRset is authenticated (i.e. has a matching - * DS RR). */ - - r = dns_transaction_negative_trust_anchor_lookup(t, dns_resource_key_name(rr->key)); - if (r < 0) - return r; - if (r > 0) - return false; - - DNS_ANSWER_FOREACH(rrsig, t->answer) { - DnsTransaction *dt; - Iterator i; - - r = dnssec_key_match_rrsig(rr->key, rrsig); - if (r < 0) - return r; - if (r == 0) - continue; - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - if (dt->key->class != rr->key->class) - continue; - - if (dt->key->type == DNS_TYPE_DNSKEY) { - - r = dns_name_equal(dns_resource_key_name(dt->key), rrsig->rrsig.signer); - if (r < 0) - return r; - if (r == 0) - continue; - - /* OK, we found an auxiliary DNSKEY - * lookup. If that lookup is - * authenticated, report this. */ - - if (dt->answer_authenticated) - return true; - - found = true; - - } else if (dt->key->type == DNS_TYPE_DS) { - - r = dns_name_equal(dns_resource_key_name(dt->key), rrsig->rrsig.signer); - if (r < 0) - return r; - if (r == 0) - continue; - - /* OK, we found an auxiliary DS - * lookup. If that lookup is - * authenticated and non-zero, we - * won! */ - - if (!dt->answer_authenticated) - return false; - - return dns_answer_match_key(dt->answer, dt->key, NULL); - } - } - } - - return found ? false : -ENXIO; -} - -static int dns_transaction_known_signed(DnsTransaction *t, DnsResourceRecord *rr) { - assert(t); - assert(rr); - - /* We know that the root domain is signed, hence if it appears - * not to be signed, there's a problem with the DNS server */ - - return rr->key->class == DNS_CLASS_IN && - dns_name_is_root(dns_resource_key_name(rr->key)); -} - -static int dns_transaction_check_revoked_trust_anchors(DnsTransaction *t) { - DnsResourceRecord *rr; - int r; - - assert(t); - - /* Maybe warn the user that we encountered a revoked DNSKEY - * for a key from our trust anchor. Note that we don't care - * whether the DNSKEY can be authenticated or not. It's - * sufficient if it is self-signed. */ - - DNS_ANSWER_FOREACH(rr, t->answer) { - r = dns_trust_anchor_check_revoked(&t->scope->manager->trust_anchor, rr, t->answer); - if (r < 0) - return r; - } - - return 0; -} - -static int dns_transaction_invalidate_revoked_keys(DnsTransaction *t) { - bool changed; - int r; - - assert(t); - - /* Removes all DNSKEY/DS objects from t->validated_keys that - * our trust anchors database considers revoked. */ - - do { - DnsResourceRecord *rr; - - changed = false; - - DNS_ANSWER_FOREACH(rr, t->validated_keys) { - r = dns_trust_anchor_is_revoked(&t->scope->manager->trust_anchor, rr); - if (r < 0) - return r; - if (r > 0) { - r = dns_answer_remove_by_rr(&t->validated_keys, rr); - if (r < 0) - return r; - - assert(r > 0); - changed = true; - break; - } - } - } while (changed); - - return 0; -} - -static int dns_transaction_copy_validated(DnsTransaction *t) { - DnsTransaction *dt; - Iterator i; - int r; - - assert(t); - - /* Copy all validated RRs from the auxiliary DNSSEC transactions into our set of validated RRs */ - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - if (DNS_TRANSACTION_IS_LIVE(dt->state)) - continue; - - if (!dt->answer_authenticated) - continue; - - r = dns_answer_extend(&t->validated_keys, dt->answer); - if (r < 0) - return r; - } - - return 0; -} - -typedef enum { - DNSSEC_PHASE_DNSKEY, /* Phase #1, only validate DNSKEYs */ - DNSSEC_PHASE_NSEC, /* Phase #2, only validate NSEC+NSEC3 */ - DNSSEC_PHASE_ALL, /* Phase #3, validate everything else */ -} Phase; - -static int dnssec_validate_records( - DnsTransaction *t, - Phase phase, - bool *have_nsec, - DnsAnswer **validated) { - - DnsResourceRecord *rr; - int r; - - /* Returns negative on error, 0 if validation failed, 1 to restart validation, 2 when finished. */ - - DNS_ANSWER_FOREACH(rr, t->answer) { - DnsResourceRecord *rrsig = NULL; - DnssecResult result; - - switch (rr->key->type) { - case DNS_TYPE_RRSIG: - continue; - - case DNS_TYPE_DNSKEY: - /* We validate DNSKEYs only in the DNSKEY and ALL phases */ - if (phase == DNSSEC_PHASE_NSEC) - continue; - break; - - case DNS_TYPE_NSEC: - case DNS_TYPE_NSEC3: - *have_nsec = true; - - /* We validate NSEC/NSEC3 only in the NSEC and ALL phases */ - if (phase == DNSSEC_PHASE_DNSKEY) - continue; - break; - - default: - /* We validate all other RRs only in the ALL phases */ - if (phase != DNSSEC_PHASE_ALL) - continue; - } - - r = dnssec_verify_rrset_search(t->answer, rr->key, t->validated_keys, USEC_INFINITY, &result, &rrsig); - if (r < 0) - return r; - - log_debug("Looking at %s: %s", strna(dns_resource_record_to_string(rr)), dnssec_result_to_string(result)); - - if (result == DNSSEC_VALIDATED) { - - if (rr->key->type == DNS_TYPE_DNSKEY) { - /* If we just validated a DNSKEY RRset, then let's add these keys to - * the set of validated keys for this transaction. */ - - r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - - /* Some of the DNSKEYs we just added might already have been revoked, - * remove them again in that case. */ - r = dns_transaction_invalidate_revoked_keys(t); - if (r < 0) - return r; - } - - /* Add the validated RRset to the new list of validated - * RRsets, and remove it from the unvalidated RRsets. - * We mark the RRset as authenticated and cacheable. */ - r = dns_answer_move_by_key(validated, &t->answer, rr->key, DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE); - if (r < 0) - return r; - - manager_dnssec_verdict(t->scope->manager, DNSSEC_SECURE, rr->key); - - /* Exit the loop, we dropped something from the answer, start from the beginning */ - return 1; - } - - /* If we haven't read all DNSKEYs yet a negative result of the validation is irrelevant, as - * there might be more DNSKEYs coming. Similar, if we haven't read all NSEC/NSEC3 RRs yet, - * we cannot do positive wildcard proofs yet, as those require the NSEC/NSEC3 RRs. */ - if (phase != DNSSEC_PHASE_ALL) - continue; - - if (result == DNSSEC_VALIDATED_WILDCARD) { - bool authenticated = false; - const char *source; - - /* This RRset validated, but as a wildcard. This means we need - * to prove via NSEC/NSEC3 that no matching non-wildcard RR exists.*/ - - /* First step, determine the source of synthesis */ - r = dns_resource_record_source(rrsig, &source); - if (r < 0) - return r; - - r = dnssec_test_positive_wildcard(*validated, - dns_resource_key_name(rr->key), - source, - rrsig->rrsig.signer, - &authenticated); - - /* Unless the NSEC proof showed that the key really doesn't exist something is off. */ - if (r == 0) - result = DNSSEC_INVALID; - else { - r = dns_answer_move_by_key(validated, &t->answer, rr->key, - authenticated ? (DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE) : 0); - if (r < 0) - return r; - - 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 */ - return 1; - } - } - - if (result == DNSSEC_NO_SIGNATURE) { - r = dns_transaction_requires_rrsig(t, rr); - if (r < 0) - return r; - if (r == 0) { - /* Data does not require signing. In that case, just copy it over, - * but remember that this is by no means authenticated.*/ - r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0); - if (r < 0) - return r; - - manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); - return 1; - } - - r = dns_transaction_known_signed(t, rr); - if (r < 0) - return r; - if (r > 0) { - /* This is an RR we know has to be signed. If it isn't this means - * the server is not attaching RRSIGs, hence complain. */ - - dns_server_packet_rrsig_missing(t->server, t->current_feature_level); - - if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) { - - /* Downgrading is OK? If so, just consider the information unsigned */ - - r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0); - if (r < 0) - return r; - - manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); - return 1; - } - - /* Otherwise, fail */ - t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER; - return 0; - } - - r = dns_transaction_in_private_tld(t, rr->key); - if (r < 0) - return r; - if (r > 0) { - char s[DNS_RESOURCE_KEY_STRING_MAX]; - - /* The data is from a TLD that is proven not to exist, and we are in downgrade - * mode, hence ignore the fact that this was not signed. */ - - log_info("Detected RRset %s is in a private DNS zone, permitting unsigned RRs.", - dns_resource_key_to_string(rr->key, s, sizeof s)); - - r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0); - if (r < 0) - return r; - - manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); - return 1; - } - } - - if (IN_SET(result, - DNSSEC_MISSING_KEY, - DNSSEC_SIGNATURE_EXPIRED, - DNSSEC_UNSUPPORTED_ALGORITHM)) { - - r = dns_transaction_dnskey_authenticated(t, rr); - if (r < 0 && r != -ENXIO) - return r; - if (r == 0) { - /* The DNSKEY transaction was not authenticated, this means there's - * no DS for this, which means it's OK if no keys are found for this signature. */ - - r = dns_answer_move_by_key(validated, &t->answer, rr->key, 0); - if (r < 0) - return r; - - manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); - return 1; - } - } - - r = dns_transaction_is_primary_response(t, rr); - if (r < 0) - return r; - if (r > 0) { - /* Look for a matching DNAME for this CNAME */ - r = dns_answer_has_dname_for_cname(t->answer, rr); - if (r < 0) - return r; - if (r == 0) { - /* Also look among the stuff we already validated */ - r = dns_answer_has_dname_for_cname(*validated, rr); - if (r < 0) - return r; - } - - 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; - return 0; - } - - /* This is a primary response, but we do have a DNAME RR - * in the RR that can replay this CNAME, hence rely on - * that, and we can remove the CNAME in favour of it. */ - } - - /* This is just some auxiliary data. Just remove the RRset and continue. */ - r = dns_answer_remove_by_key(&t->answer, rr->key); - if (r < 0) - return r; - - /* We dropped something from the answer, start from the beginning. */ - return 1; - } - - return 2; /* Finito. */ -} - -int dns_transaction_validate_dnssec(DnsTransaction *t) { - _cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL; - Phase phase; - DnsAnswerFlags flags; - int r; - char key_str[DNS_RESOURCE_KEY_STRING_MAX]; - - assert(t); - - /* We have now collected all DS and DNSKEY RRs in - * t->validated_keys, let's see which RRs we can now - * authenticate with that. */ - - if (t->scope->dnssec_mode == DNSSEC_NO) - return 0; - - /* Already validated */ - if (t->answer_dnssec_result != _DNSSEC_RESULT_INVALID) - return 0; - - /* Our own stuff needs no validation */ - if (IN_SET(t->answer_source, DNS_TRANSACTION_ZONE, DNS_TRANSACTION_TRUST_ANCHOR)) { - t->answer_dnssec_result = DNSSEC_VALIDATED; - t->answer_authenticated = true; - return 0; - } - - /* Cached stuff is not affected by validation. */ - if (t->answer_source != DNS_TRANSACTION_NETWORK) - return 0; - - if (!dns_transaction_dnssec_supported_full(t)) { - /* The server does not support DNSSEC, or doesn't augment responses with RRSIGs. */ - t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER; - log_debug("Not validating response for %" PRIu16 ", used server feature level does not support DNSSEC.", t->id); - return 0; - } - - log_debug("Validating response from transaction %" PRIu16 " (%s).", - t->id, - dns_resource_key_to_string(t->key, key_str, sizeof key_str)); - - /* First, see if this response contains any revoked trust - * anchors we care about */ - r = dns_transaction_check_revoked_trust_anchors(t); - if (r < 0) - return r; - - /* Third, copy all RRs we acquired successfully from auxiliary RRs over. */ - r = dns_transaction_copy_validated(t); - if (r < 0) - return r; - - /* Second, see if there are DNSKEYs we already know a - * validated DS for. */ - r = dns_transaction_validate_dnskey_by_ds(t); - if (r < 0) - return r; - - /* Fourth, remove all DNSKEY and DS RRs again that our trust - * anchor says are revoked. After all we might have marked - * some keys revoked above, but they might still be lingering - * in our validated_keys list. */ - r = dns_transaction_invalidate_revoked_keys(t); - if (r < 0) - return r; - - phase = DNSSEC_PHASE_DNSKEY; - for (;;) { - bool have_nsec = false; - - r = dnssec_validate_records(t, phase, &have_nsec, &validated); - if (r <= 0) - return r; - - /* Try again as long as we managed to achieve something */ - if (r == 1) - continue; - - if (phase == DNSSEC_PHASE_DNSKEY && have_nsec) { - /* OK, we processed all DNSKEYs, and there are NSEC/NSEC3 RRs, look at those now. */ - phase = DNSSEC_PHASE_NSEC; - continue; - } - - if (phase != DNSSEC_PHASE_ALL) { - /* OK, we processed all DNSKEYs and NSEC/NSEC3 RRs, look at all the rest now. - * Note that in this third phase we start to remove RRs we couldn't validate. */ - phase = DNSSEC_PHASE_ALL; - continue; - } - - /* We're done */ - break; - } - - dns_answer_unref(t->answer); - t->answer = validated; - validated = NULL; - - /* At this point the answer only contains validated - * RRsets. Now, let's see if it actually answers the question - * we asked. If so, great! If it doesn't, then see if - * NSEC/NSEC3 can prove this. */ - r = dns_transaction_has_positive_answer(t, &flags); - if (r > 0) { - /* Yes, it answers the question! */ - - if (flags & DNS_ANSWER_AUTHENTICATED) { - /* The answer is fully authenticated, yay. */ - t->answer_dnssec_result = DNSSEC_VALIDATED; - t->answer_rcode = DNS_RCODE_SUCCESS; - t->answer_authenticated = true; - } else { - /* The answer is not fully authenticated. */ - t->answer_dnssec_result = DNSSEC_UNSIGNED; - t->answer_authenticated = false; - } - - } else if (r == 0) { - DnssecNsecResult nr; - bool authenticated = false; - - /* Bummer! Let's check NSEC/NSEC3 */ - r = dnssec_nsec_test(t->answer, t->key, &nr, &authenticated, &t->answer_nsec_ttl); - if (r < 0) - return r; - - switch (nr) { - - case DNSSEC_NSEC_NXDOMAIN: - /* NSEC proves the domain doesn't exist. Very good. */ - log_debug("Proved NXDOMAIN via NSEC/NSEC3 for transaction %u (%s)", t->id, key_str); - 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: - /* NSEC proves that there's no data here, very good. */ - log_debug("Proved NODATA via NSEC/NSEC3 for transaction %u (%s)", t->id, key_str); - 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: - /* NSEC3 says the data might not be signed */ - log_debug("Data is NSEC3 opt-out via NSEC/NSEC3 for transaction %u (%s)", t->id, key_str); - 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: - /* No NSEC data? Bummer! */ - - r = dns_transaction_requires_nsec(t); - if (r < 0) - return r; - if (r > 0) { - t->answer_dnssec_result = DNSSEC_NO_SIGNATURE; - 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; - - 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: - assert_not_reached("Unexpected NSEC result."); - } - } - - return 1; -} - -static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] = { - [DNS_TRANSACTION_NULL] = "null", - [DNS_TRANSACTION_PENDING] = "pending", - [DNS_TRANSACTION_VALIDATING] = "validating", - [DNS_TRANSACTION_RCODE_FAILURE] = "rcode-failure", - [DNS_TRANSACTION_SUCCESS] = "success", - [DNS_TRANSACTION_NO_SERVERS] = "no-servers", - [DNS_TRANSACTION_TIMEOUT] = "timeout", - [DNS_TRANSACTION_ATTEMPTS_MAX_REACHED] = "attempts-max-reached", - [DNS_TRANSACTION_INVALID_REPLY] = "invalid-reply", - [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); - -static const char* const dns_transaction_source_table[_DNS_TRANSACTION_SOURCE_MAX] = { - [DNS_TRANSACTION_NETWORK] = "network", - [DNS_TRANSACTION_CACHE] = "cache", - [DNS_TRANSACTION_ZONE] = "zone", - [DNS_TRANSACTION_TRUST_ANCHOR] = "trust-anchor", -}; -DEFINE_STRING_TABLE_LOOKUP(dns_transaction_source, DnsTransactionSource); diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h deleted file mode 100644 index 5a1df70422..0000000000 --- a/src/resolve/resolved-dns-transaction.h +++ /dev/null @@ -1,181 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -typedef struct DnsTransaction DnsTransaction; -typedef enum DnsTransactionState DnsTransactionState; -typedef enum DnsTransactionSource DnsTransactionSource; - -enum DnsTransactionState { - DNS_TRANSACTION_NULL, - DNS_TRANSACTION_PENDING, - DNS_TRANSACTION_VALIDATING, - DNS_TRANSACTION_RCODE_FAILURE, - DNS_TRANSACTION_SUCCESS, - DNS_TRANSACTION_NO_SERVERS, - DNS_TRANSACTION_TIMEOUT, - DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, - DNS_TRANSACTION_INVALID_REPLY, - 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 -}; - -#define DNS_TRANSACTION_IS_LIVE(state) IN_SET((state), DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING) - -enum DnsTransactionSource { - DNS_TRANSACTION_NETWORK, - DNS_TRANSACTION_CACHE, - DNS_TRANSACTION_ZONE, - DNS_TRANSACTION_TRUST_ANCHOR, - _DNS_TRANSACTION_SOURCE_MAX, - _DNS_TRANSACTION_SOURCE_INVALID = -1 -}; - -#include "resolved-dns-answer.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-question.h" -#include "resolved-dns-scope.h" -#include "resolved-dns-server.h" -#include "resolved-dns-stream.h" - -struct DnsTransaction { - DnsScope *scope; - - DnsResourceKey *key; - - DnsTransactionState state; - - uint16_t id; - - bool tried_stream:1; - - bool initial_jitter_scheduled:1; - bool initial_jitter_elapsed:1; - - bool clamp_ttl:1; - - DnsPacket *sent, *received; - - DnsAnswer *answer; - int answer_rcode; - 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 - * question are authenticated, or, if there are none, whether - * the NODATA or NXDOMAIN case is. It says nothing about - * additional RRs listed in the answer, however they have - * their own DNS_ANSWER_AUTHORIZED FLAGS. Note that this bit - * is defined different than the AD bit in DNS packets, as - * that covers more than just the actual primary answer. */ - bool answer_authenticated; - - /* Contains DNSKEY, DS, SOA RRs we already verified and need - * to authenticate this reply */ - DnsAnswer *validated_keys; - - usec_t start_usec; - usec_t next_attempt_after; - sd_event_source *timeout_event_source; - unsigned n_attempts; - - /* UDP connection logic, if we need it */ - int dns_udp_fd; - sd_event_source *dns_udp_event_source; - - /* TCP connection logic, if we need it */ - DnsStream *stream; - - /* The active server */ - DnsServer *server; - - /* The features of the DNS server at time of transaction start */ - DnsServerFeatureLevel current_feature_level; - - /* If we got SERVFAIL back, we retry the lookup, using a lower feature level than we used before. */ - DnsServerFeatureLevel clamp_feature_level; - - /* Query candidates this transaction is referenced by and that - * shall be notified about this specific transaction - * completing. */ - Set *notify_query_candidates, *notify_query_candidates_done; - - /* Zone items this transaction is referenced by and that shall - * be notified about completion. */ - Set *notify_zone_items, *notify_zone_items_done; - - /* Other transactions that this transactions is referenced by - * and that shall be notified about completion. This is used - * when transactions want to validate their RRsets, but need - * another DNSKEY or DS RR to do so. */ - Set *notify_transactions, *notify_transactions_done; - - /* The opposite direction: the transactions this transaction - * created in order to request DNSKEY or DS RRs. */ - Set *dnssec_transactions; - - unsigned block_gc; - - LIST_FIELDS(DnsTransaction, transactions_by_scope); -}; - -int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key); -DnsTransaction* dns_transaction_free(DnsTransaction *t); - -bool dns_transaction_gc(DnsTransaction *t); -int dns_transaction_go(DnsTransaction *t); - -void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p); -void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state); - -void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source); -int dns_transaction_validate_dnssec(DnsTransaction *t); -int dns_transaction_request_dnssec_keys(DnsTransaction *t); - -const char* dns_transaction_state_to_string(DnsTransactionState p) _const_; -DnsTransactionState dns_transaction_state_from_string(const char *s) _pure_; - -const char* dns_transaction_source_to_string(DnsTransactionSource p) _const_; -DnsTransactionSource dns_transaction_source_from_string(const char *s) _pure_; - -/* LLMNR Jitter interval, see RFC 4795 Section 7 */ -#define LLMNR_JITTER_INTERVAL_USEC (100 * USEC_PER_MSEC) - -/* mDNS Jitter interval, see RFC 6762 Section 5.2 */ -#define MDNS_JITTER_MIN_USEC (20 * USEC_PER_MSEC) -#define MDNS_JITTER_RANGE_USEC (100 * USEC_PER_MSEC) - -/* Maximum attempts to send DNS requests, across all DNS servers */ -#define DNS_TRANSACTION_ATTEMPTS_MAX 16 - -/* Maximum attempts to send LLMNR requests, see RFC 4795 Section 2.7 */ -#define LLMNR_TRANSACTION_ATTEMPTS_MAX 3 - -#define TRANSACTION_ATTEMPTS_MAX(p) ((p) == DNS_PROTOCOL_LLMNR ? LLMNR_TRANSACTION_ATTEMPTS_MAX : DNS_TRANSACTION_ATTEMPTS_MAX) diff --git a/src/resolve/resolved-dns-trust-anchor.c b/src/resolve/resolved-dns-trust-anchor.c deleted file mode 100644 index 9917b9e984..0000000000 --- a/src/resolve/resolved-dns-trust-anchor.c +++ /dev/null @@ -1,746 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 <sd-messages.h> - -#include "alloc-util.h" -#include "conf-files.h" -#include "def.h" -#include "dns-domain.h" -#include "fd-util.h" -#include "fileio.h" -#include "hexdecoct.h" -#include "parse-util.h" -#include "resolved-dns-trust-anchor.h" -#include "resolved-dns-dnssec.h" -#include "set.h" -#include "string-util.h" -#include "strv.h" - -static const char trust_anchor_dirs[] = CONF_PATHS_NULSTR("dnssec-trust-anchors.d"); - -/* The DS RR from https://data.iana.org/root-anchors/root-anchors.xml, retrieved December 2015 */ -static const uint8_t root_digest[] = - { 0x49, 0xAA, 0xC1, 0x1D, 0x7B, 0x6F, 0x64, 0x46, 0x70, 0x2E, 0x54, 0xA1, 0x60, 0x73, 0x71, 0x60, - 0x7A, 0x1A, 0x41, 0x85, 0x52, 0x00, 0xFD, 0x2C, 0xE1, 0xCD, 0xDE, 0x32, 0xF2, 0x4E, 0x8F, 0xB5 }; - -static bool dns_trust_anchor_knows_domain_positive(DnsTrustAnchor *d, const char *name) { - assert(d); - - /* Returns true if there's an entry for the specified domain - * name in our trust anchor */ - - return - hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DNSKEY, name)) || - hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DS, name)); -} - -static int dns_trust_anchor_add_builtin_positive(DnsTrustAnchor *d) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - int r; - - assert(d); - - r = hashmap_ensure_allocated(&d->positive_by_key, &dns_resource_key_hash_ops); - if (r < 0) - return r; - - /* Only add the built-in trust anchor if there's neither a DS - * nor a DNSKEY defined for the root domain. That way users - * have an easy way to override the root domain DS/DNSKEY - * data. */ - if (dns_trust_anchor_knows_domain_positive(d, ".")) - return 0; - - /* Add the RR from https://data.iana.org/root-anchors/root-anchors.xml */ - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, ""); - if (!rr) - return -ENOMEM; - - rr->ds.key_tag = 19036; - rr->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; - rr->ds.digest_type = DNSSEC_DIGEST_SHA256; - rr->ds.digest_size = sizeof(root_digest); - rr->ds.digest = memdup(root_digest, rr->ds.digest_size); - if (!rr->ds.digest) - return -ENOMEM; - - answer = dns_answer_new(1); - if (!answer) - return -ENOMEM; - - r = dns_answer_add(answer, rr, 0, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - - r = hashmap_put(d->positive_by_key, rr->key, answer); - if (r < 0) - return r; - - answer = NULL; - return 0; -} - -static int dns_trust_anchor_add_builtin_negative(DnsTrustAnchor *d) { - - static const char private_domains[] = - /* RFC 6761 says that .test is a special domain for - * testing and not to be installed in the root zone */ - "test\0" - - /* RFC 6761 says that these reverse IP lookup ranges - * are for private addresses, and hence should not - * show up in the root zone */ - "10.in-addr.arpa\0" - "16.172.in-addr.arpa\0" - "17.172.in-addr.arpa\0" - "18.172.in-addr.arpa\0" - "19.172.in-addr.arpa\0" - "20.172.in-addr.arpa\0" - "21.172.in-addr.arpa\0" - "22.172.in-addr.arpa\0" - "23.172.in-addr.arpa\0" - "24.172.in-addr.arpa\0" - "25.172.in-addr.arpa\0" - "26.172.in-addr.arpa\0" - "27.172.in-addr.arpa\0" - "28.172.in-addr.arpa\0" - "29.172.in-addr.arpa\0" - "30.172.in-addr.arpa\0" - "31.172.in-addr.arpa\0" - "168.192.in-addr.arpa\0" - - /* The same, but for IPv6. */ - "d.f.ip6.arpa\0" - - /* RFC 6762 reserves the .local domain for Multicast - * DNS, it hence cannot appear in the root zone. (Note - * that we by default do not route .local traffic to - * DNS anyway, except when a configured search domain - * suggests so.) */ - "local\0" - - /* These two are well known, popular private zone - * TLDs, that are blocked from delegation, according - * to: - * http://icannwiki.com/Name_Collision#NGPC_Resolution - * - * There's also ongoing work on making this official - * in an RRC: - * https://www.ietf.org/archive/id/draft-chapin-additional-reserved-tlds-02.txt */ - "home\0" - "corp\0" - - /* The following four TLDs are suggested for private - * zones in RFC 6762, Appendix G, and are hence very - * unlikely to be made official TLDs any day soon */ - "lan\0" - "intranet\0" - "internal\0" - "private\0"; - - const char *name; - int r; - - assert(d); - - /* Only add the built-in trust anchor if there's no negative - * trust anchor defined at all. This enables easy overriding - * of negative trust anchors. */ - - if (set_size(d->negative_by_name) > 0) - return 0; - - r = set_ensure_allocated(&d->negative_by_name, &dns_name_hash_ops); - if (r < 0) - return r; - - /* We add a couple of domains as default negative trust - * anchors, where it's very unlikely they will be installed in - * the root zone. If they exist they must be private, and thus - * unsigned. */ - - NULSTR_FOREACH(name, private_domains) { - - if (dns_trust_anchor_knows_domain_positive(d, name)) - continue; - - r = set_put_strdup(d->negative_by_name, name); - if (r < 0) - return r; - } - - return 0; -} - -static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, unsigned line, const char *s) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_free_ char *domain = NULL, *class = NULL, *type = NULL; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - DnsAnswer *old_answer = NULL; - const char *p = s; - int r; - - assert(d); - assert(line); - - r = extract_first_word(&p, &domain, NULL, EXTRACT_QUOTES); - if (r < 0) - return log_warning_errno(r, "Unable to parse domain in line %s:%u: %m", path, line); - - if (!dns_name_is_valid(domain)) { - log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain, path, line); - return -EINVAL; - } - - r = extract_many_words(&p, NULL, 0, &class, &type, NULL); - if (r < 0) - return log_warning_errno(r, "Unable to parse class and type in line %s:%u: %m", path, line); - if (r != 2) { - log_warning("Missing class or type in line %s:%u", path, line); - return -EINVAL; - } - - if (!strcaseeq(class, "IN")) { - log_warning("RR class %s is not supported, ignoring line %s:%u.", class, path, line); - return -EINVAL; - } - - if (strcaseeq(type, "DS")) { - _cleanup_free_ char *key_tag = NULL, *algorithm = NULL, *digest_type = NULL, *digest = NULL; - _cleanup_free_ void *dd = NULL; - uint16_t kt; - int a, dt; - size_t l; - - r = extract_many_words(&p, NULL, 0, &key_tag, &algorithm, &digest_type, &digest, NULL); - if (r < 0) { - log_warning_errno(r, "Failed to parse DS parameters on line %s:%u: %m", path, line); - return -EINVAL; - } - if (r != 4) { - log_warning("Missing DS parameters on line %s:%u", path, line); - return -EINVAL; - } - - r = safe_atou16(key_tag, &kt); - if (r < 0) - return log_warning_errno(r, "Failed to parse DS key tag %s on line %s:%u: %m", key_tag, path, line); - - a = dnssec_algorithm_from_string(algorithm); - if (a < 0) { - log_warning("Failed to parse DS algorithm %s on line %s:%u", algorithm, path, line); - return -EINVAL; - } - - dt = dnssec_digest_from_string(digest_type); - if (dt < 0) { - log_warning("Failed to parse DS digest type %s on line %s:%u", digest_type, path, line); - return -EINVAL; - } - - r = unhexmem(digest, strlen(digest), &dd, &l); - if (r < 0) { - log_warning("Failed to parse DS digest %s on line %s:%u", digest, path, line); - return -EINVAL; - } - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, domain); - if (!rr) - return log_oom(); - - rr->ds.key_tag = kt; - rr->ds.algorithm = a; - rr->ds.digest_type = dt; - rr->ds.digest_size = l; - rr->ds.digest = dd; - dd = NULL; - - } else if (strcaseeq(type, "DNSKEY")) { - _cleanup_free_ char *flags = NULL, *protocol = NULL, *algorithm = NULL, *key = NULL; - _cleanup_free_ void *k = NULL; - uint16_t f; - size_t l; - int a; - - r = extract_many_words(&p, NULL, 0, &flags, &protocol, &algorithm, &key, NULL); - if (r < 0) - return log_warning_errno(r, "Failed to parse DNSKEY parameters on line %s:%u: %m", path, line); - if (r != 4) { - log_warning("Missing DNSKEY parameters on line %s:%u", path, line); - return -EINVAL; - } - - if (!streq(protocol, "3")) { - log_warning("DNSKEY Protocol is not 3 on line %s:%u", path, line); - return -EINVAL; - } - - r = safe_atou16(flags, &f); - if (r < 0) - return log_warning_errno(r, "Failed to parse DNSKEY flags field %s on line %s:%u", flags, path, line); - if ((f & DNSKEY_FLAG_ZONE_KEY) == 0) { - log_warning("DNSKEY lacks zone key bit set on line %s:%u", path, line); - return -EINVAL; - } - if ((f & DNSKEY_FLAG_REVOKE)) { - log_warning("DNSKEY is already revoked on line %s:%u", path, line); - return -EINVAL; - } - - a = dnssec_algorithm_from_string(algorithm); - if (a < 0) { - log_warning("Failed to parse DNSKEY algorithm %s on line %s:%u", algorithm, path, line); - return -EINVAL; - } - - r = unbase64mem(key, strlen(key), &k, &l); - if (r < 0) - return log_warning_errno(r, "Failed to parse DNSKEY key data %s on line %s:%u", key, path, line); - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, domain); - if (!rr) - return log_oom(); - - rr->dnskey.flags = f; - rr->dnskey.protocol = 3; - rr->dnskey.algorithm = a; - rr->dnskey.key_size = l; - rr->dnskey.key = k; - k = NULL; - - } else { - log_warning("RR type %s is not supported, ignoring line %s:%u.", type, path, line); - return -EINVAL; - } - - if (!isempty(p)) { - log_warning("Trailing garbage on line %s:%u, ignoring line.", path, line); - return -EINVAL; - } - - r = hashmap_ensure_allocated(&d->positive_by_key, &dns_resource_key_hash_ops); - if (r < 0) - return log_oom(); - - old_answer = hashmap_get(d->positive_by_key, rr->key); - answer = dns_answer_ref(old_answer); - - r = dns_answer_add_extend(&answer, rr, 0, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return log_error_errno(r, "Failed to add trust anchor RR: %m"); - - r = hashmap_replace(d->positive_by_key, rr->key, answer); - if (r < 0) - return log_error_errno(r, "Failed to add answer to trust anchor: %m"); - - old_answer = dns_answer_unref(old_answer); - answer = NULL; - - return 0; -} - -static int dns_trust_anchor_load_negative(DnsTrustAnchor *d, const char *path, unsigned line, const char *s) { - _cleanup_free_ char *domain = NULL; - const char *p = s; - int r; - - assert(d); - assert(line); - - r = extract_first_word(&p, &domain, NULL, EXTRACT_QUOTES); - if (r < 0) - return log_warning_errno(r, "Unable to parse line %s:%u: %m", path, line); - - if (!dns_name_is_valid(domain)) { - log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain, path, line); - return -EINVAL; - } - - if (!isempty(p)) { - log_warning("Trailing garbage at line %s:%u, ignoring line.", path, line); - return -EINVAL; - } - - r = set_ensure_allocated(&d->negative_by_name, &dns_name_hash_ops); - if (r < 0) - return log_oom(); - - r = set_put(d->negative_by_name, domain); - if (r < 0) - return log_oom(); - if (r > 0) - domain = NULL; - - return 0; -} - -static int dns_trust_anchor_load_files( - DnsTrustAnchor *d, - const char *suffix, - int (*loader)(DnsTrustAnchor *d, const char *path, unsigned n, const char *line)) { - - _cleanup_strv_free_ char **files = NULL; - char **f; - int r; - - assert(d); - assert(suffix); - assert(loader); - - r = conf_files_list_nulstr(&files, suffix, NULL, trust_anchor_dirs); - if (r < 0) - return log_error_errno(r, "Failed to enumerate %s trust anchor files: %m", suffix); - - STRV_FOREACH(f, files) { - _cleanup_fclose_ FILE *g = NULL; - char line[LINE_MAX]; - unsigned n = 0; - - g = fopen(*f, "r"); - if (!g) { - if (errno == ENOENT) - continue; - - log_warning_errno(errno, "Failed to open %s: %m", *f); - continue; - } - - FOREACH_LINE(line, g, log_warning_errno(errno, "Failed to read %s, ignoring: %m", *f)) { - char *l; - - n++; - - l = strstrip(line); - if (isempty(l)) - continue; - - if (*l == ';') - continue; - - (void) loader(d, *f, n, l); - } - } - - return 0; -} - -static int domain_name_cmp(const void *a, const void *b) { - char **x = (char**) a, **y = (char**) b; - - return dns_name_compare_func(*x, *y); -} - -static int dns_trust_anchor_dump(DnsTrustAnchor *d) { - DnsAnswer *a; - Iterator i; - - assert(d); - - if (hashmap_isempty(d->positive_by_key)) - log_info("No positive trust anchors defined."); - else { - log_info("Positive Trust Anchors:"); - HASHMAP_FOREACH(a, d->positive_by_key, i) { - DnsResourceRecord *rr; - - DNS_ANSWER_FOREACH(rr, a) - log_info("%s", dns_resource_record_to_string(rr)); - } - } - - if (set_isempty(d->negative_by_name)) - log_info("No negative trust anchors defined."); - else { - _cleanup_free_ char **l = NULL, *j = NULL; - - l = set_get_strv(d->negative_by_name); - if (!l) - return log_oom(); - - qsort_safe(l, set_size(d->negative_by_name), sizeof(char*), domain_name_cmp); - - j = strv_join(l, " "); - if (!j) - return log_oom(); - - log_info("Negative trust anchors: %s", j); - } - - return 0; -} - -int dns_trust_anchor_load(DnsTrustAnchor *d) { - int r; - - assert(d); - - /* If loading things from disk fails, we don't consider this fatal */ - (void) dns_trust_anchor_load_files(d, ".positive", dns_trust_anchor_load_positive); - (void) dns_trust_anchor_load_files(d, ".negative", dns_trust_anchor_load_negative); - - /* However, if the built-in DS fails, then we have a problem. */ - r = dns_trust_anchor_add_builtin_positive(d); - if (r < 0) - return log_error_errno(r, "Failed to add built-in positive trust anchor: %m"); - - r = dns_trust_anchor_add_builtin_negative(d); - if (r < 0) - return log_error_errno(r, "Failed to add built-in negative trust anchor: %m"); - - dns_trust_anchor_dump(d); - - return 0; -} - -void dns_trust_anchor_flush(DnsTrustAnchor *d) { - DnsAnswer *a; - DnsResourceRecord *rr; - - assert(d); - - while ((a = hashmap_steal_first(d->positive_by_key))) - dns_answer_unref(a); - d->positive_by_key = hashmap_free(d->positive_by_key); - - while ((rr = set_steal_first(d->revoked_by_rr))) - dns_resource_record_unref(rr); - d->revoked_by_rr = set_free(d->revoked_by_rr); - - d->negative_by_name = set_free_free(d->negative_by_name); -} - -int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey *key, DnsAnswer **ret) { - DnsAnswer *a; - - assert(d); - assert(key); - assert(ret); - - /* We only serve DS and DNSKEY RRs. */ - if (!IN_SET(key->type, DNS_TYPE_DS, DNS_TYPE_DNSKEY)) - return 0; - - a = hashmap_get(d->positive_by_key, key); - if (!a) - return 0; - - *ret = dns_answer_ref(a); - return 1; -} - -int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name) { - assert(d); - assert(name); - - return set_contains(d->negative_by_name, name); -} - -static int dns_trust_anchor_revoked_put(DnsTrustAnchor *d, DnsResourceRecord *rr) { - int r; - - assert(d); - - r = set_ensure_allocated(&d->revoked_by_rr, &dns_resource_record_hash_ops); - if (r < 0) - return r; - - r = set_put(d->revoked_by_rr, rr); - if (r < 0) - return r; - if (r > 0) - dns_resource_record_ref(rr); - - return r; -} - -static int dns_trust_anchor_remove_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr) { - _cleanup_(dns_answer_unrefp) DnsAnswer *new_answer = NULL; - DnsAnswer *old_answer; - int r; - - /* Remember that this is a revoked trust anchor RR */ - r = dns_trust_anchor_revoked_put(d, rr); - if (r < 0) - return r; - - /* Remove this from the positive trust anchor */ - old_answer = hashmap_get(d->positive_by_key, rr->key); - if (!old_answer) - return 0; - - new_answer = dns_answer_ref(old_answer); - - r = dns_answer_remove_by_rr(&new_answer, rr); - if (r <= 0) - return r; - - /* We found the key! Warn the user */ - log_struct(LOG_WARNING, - LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED), - LOG_MESSAGE("DNSSEC Trust anchor %s has been revoked. Please update the trust anchor, or upgrade your operating system."), strna(dns_resource_record_to_string(rr)), - "TRUST_ANCHOR=%s", dns_resource_record_to_string(rr), - NULL); - - if (dns_answer_size(new_answer) <= 0) { - assert_se(hashmap_remove(d->positive_by_key, rr->key) == old_answer); - dns_answer_unref(old_answer); - return 1; - } - - r = hashmap_replace(d->positive_by_key, new_answer->items[0].rr->key, new_answer); - if (r < 0) - return r; - - new_answer = NULL; - dns_answer_unref(old_answer); - return 1; -} - -static int dns_trust_anchor_check_revoked_one(DnsTrustAnchor *d, DnsResourceRecord *revoked_dnskey) { - DnsAnswer *a; - int r; - - assert(d); - assert(revoked_dnskey); - assert(revoked_dnskey->key->type == DNS_TYPE_DNSKEY); - assert(revoked_dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE); - - a = hashmap_get(d->positive_by_key, revoked_dnskey->key); - if (a) { - DnsResourceRecord *anchor; - - /* First, look for the precise DNSKEY in our trust anchor database */ - - DNS_ANSWER_FOREACH(anchor, a) { - - if (anchor->dnskey.protocol != revoked_dnskey->dnskey.protocol) - continue; - - if (anchor->dnskey.algorithm != revoked_dnskey->dnskey.algorithm) - continue; - - if (anchor->dnskey.key_size != revoked_dnskey->dnskey.key_size) - continue; - - /* Note that we allow the REVOKE bit to be - * different! It will be set in the revoked - * key, but unset in our version of it */ - if (((anchor->dnskey.flags ^ revoked_dnskey->dnskey.flags) | DNSKEY_FLAG_REVOKE) != DNSKEY_FLAG_REVOKE) - continue; - - if (memcmp(anchor->dnskey.key, revoked_dnskey->dnskey.key, anchor->dnskey.key_size) != 0) - continue; - - dns_trust_anchor_remove_revoked(d, anchor); - break; - } - } - - a = hashmap_get(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(revoked_dnskey->key->class, DNS_TYPE_DS, dns_resource_key_name(revoked_dnskey->key))); - if (a) { - DnsResourceRecord *anchor; - - /* Second, look for DS RRs matching this DNSKEY in our trust anchor database */ - - DNS_ANSWER_FOREACH(anchor, a) { - - /* We set mask_revoke to true here, since our - * DS fingerprint will be the one of the - * unrevoked DNSKEY, but the one we got passed - * here has the bit set. */ - r = dnssec_verify_dnskey_by_ds(revoked_dnskey, anchor, true); - if (r < 0) - return r; - if (r == 0) - continue; - - dns_trust_anchor_remove_revoked(d, anchor); - break; - } - } - - return 0; -} - -int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsResourceRecord *dnskey, DnsAnswer *rrs) { - DnsResourceRecord *rrsig; - int r; - - assert(d); - assert(dnskey); - - /* Looks if "dnskey" is a self-signed RR that has been revoked - * and matches one of our trust anchor entries. If so, removes - * it from the trust anchor and returns > 0. */ - - if (dnskey->key->type != DNS_TYPE_DNSKEY) - return 0; - - /* Is this DNSKEY revoked? */ - if ((dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE) == 0) - return 0; - - /* Could this be interesting to us at all? If not, - * there's no point in looking for and verifying a - * self-signed RRSIG. */ - if (!dns_trust_anchor_knows_domain_positive(d, dns_resource_key_name(dnskey->key))) - return 0; - - /* Look for a self-signed RRSIG in the other rrs belonging to this DNSKEY */ - DNS_ANSWER_FOREACH(rrsig, rrs) { - DnssecResult result; - - if (rrsig->key->type != DNS_TYPE_RRSIG) - continue; - - r = dnssec_rrsig_match_dnskey(rrsig, dnskey, true); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dnssec_verify_rrset(rrs, dnskey->key, rrsig, dnskey, USEC_INFINITY, &result); - if (r < 0) - return r; - if (result != DNSSEC_VALIDATED) - continue; - - /* Bingo! This is a revoked self-signed DNSKEY. Let's - * see if this precise one exists in our trust anchor - * database, too. */ - r = dns_trust_anchor_check_revoked_one(d, dnskey); - if (r < 0) - return r; - - return 1; - } - - return 0; -} - -int dns_trust_anchor_is_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr) { - assert(d); - - if (!IN_SET(rr->key->type, DNS_TYPE_DS, DNS_TYPE_DNSKEY)) - return 0; - - return set_contains(d->revoked_by_rr, rr); -} diff --git a/src/resolve/resolved-dns-trust-anchor.h b/src/resolve/resolved-dns-trust-anchor.h deleted file mode 100644 index 635c75fde5..0000000000 --- a/src/resolve/resolved-dns-trust-anchor.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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/>. -***/ - -typedef struct DnsTrustAnchor DnsTrustAnchor; - -#include "hashmap.h" -#include "resolved-dns-answer.h" -#include "resolved-dns-rr.h" - -/* This contains a fixed database mapping domain names to DS or DNSKEY records. */ - -struct DnsTrustAnchor { - Hashmap *positive_by_key; - Set *negative_by_name; - Set *revoked_by_rr; -}; - -int dns_trust_anchor_load(DnsTrustAnchor *d); -void dns_trust_anchor_flush(DnsTrustAnchor *d); - -int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey* key, DnsAnswer **answer); -int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name); - -int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsResourceRecord *dnskey, DnsAnswer *rrs); -int dns_trust_anchor_is_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr); diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c deleted file mode 100644 index 746a979f47..0000000000 --- a/src/resolve/resolved-dns-zone.c +++ /dev/null @@ -1,664 +0,0 @@ -/*** - 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 "dns-domain.h" -#include "list.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-zone.h" -#include "string-util.h" - -/* Never allow more than 1K entries */ -#define ZONE_MAX 1024 - -void dns_zone_item_probe_stop(DnsZoneItem *i) { - DnsTransaction *t; - assert(i); - - if (!i->probe_transaction) - return; - - t = i->probe_transaction; - i->probe_transaction = NULL; - - set_remove(t->notify_zone_items, i); - set_remove(t->notify_zone_items_done, i); - dns_transaction_gc(t); -} - -static void dns_zone_item_free(DnsZoneItem *i) { - if (!i) - return; - - dns_zone_item_probe_stop(i); - dns_resource_record_unref(i->rr); - - free(i); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free); - -static void dns_zone_item_remove_and_free(DnsZone *z, DnsZoneItem *i) { - DnsZoneItem *first; - - assert(z); - - if (!i) - return; - - first = hashmap_get(z->by_key, i->rr->key); - LIST_REMOVE(by_key, first, i); - if (first) - assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0); - else - hashmap_remove(z->by_key, i->rr->key); - - first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key)); - LIST_REMOVE(by_name, first, i); - if (first) - assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0); - else - hashmap_remove(z->by_name, dns_resource_key_name(i->rr->key)); - - dns_zone_item_free(i); -} - -void dns_zone_flush(DnsZone *z) { - DnsZoneItem *i; - - assert(z); - - while ((i = hashmap_first(z->by_key))) - dns_zone_item_remove_and_free(z, i); - - assert(hashmap_size(z->by_key) == 0); - assert(hashmap_size(z->by_name) == 0); - - z->by_key = hashmap_free(z->by_key); - z->by_name = hashmap_free(z->by_name); -} - -static DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) { - DnsZoneItem *i; - - assert(z); - assert(rr); - - LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key)) - if (dns_resource_record_equal(i->rr, rr) > 0) - return i; - - return NULL; -} - -void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) { - DnsZoneItem *i; - - assert(z); - assert(rr); - - i = dns_zone_get(z, rr); - if (i) - dns_zone_item_remove_and_free(z, i); -} - -static int dns_zone_init(DnsZone *z) { - int r; - - assert(z); - - r = hashmap_ensure_allocated(&z->by_key, &dns_resource_key_hash_ops); - if (r < 0) - return r; - - r = hashmap_ensure_allocated(&z->by_name, &dns_name_hash_ops); - if (r < 0) - return r; - - return 0; -} - -static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) { - DnsZoneItem *first; - int r; - - first = hashmap_get(z->by_key, i->rr->key); - if (first) { - LIST_PREPEND(by_key, first, i); - assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0); - } else { - r = hashmap_put(z->by_key, i->rr->key, i); - if (r < 0) - return r; - } - - first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key)); - if (first) { - LIST_PREPEND(by_name, first, i); - assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0); - } else { - r = hashmap_put(z->by_name, dns_resource_key_name(i->rr->key), i); - if (r < 0) - return r; - } - - return 0; -} - -static int dns_zone_item_probe_start(DnsZoneItem *i) { - DnsTransaction *t; - int r; - - assert(i); - - if (i->probe_transaction) - return 0; - - t = dns_scope_find_transaction(i->scope, &DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key)), false); - if (!t) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - - key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key)); - if (!key) - return -ENOMEM; - - r = dns_transaction_new(&t, i->scope, key); - if (r < 0) - return r; - } - - r = set_ensure_allocated(&t->notify_zone_items, NULL); - if (r < 0) - goto gc; - - r = set_ensure_allocated(&t->notify_zone_items_done, NULL); - if (r < 0) - goto gc; - - r = set_put(t->notify_zone_items, i); - if (r < 0) - goto gc; - - i->probe_transaction = t; - - if (t->state == DNS_TRANSACTION_NULL) { - - i->block_ready++; - r = dns_transaction_go(t); - i->block_ready--; - - if (r < 0) { - dns_zone_item_probe_stop(i); - return r; - } - } - - dns_zone_item_notify(i); - return 0; - -gc: - dns_transaction_gc(t); - return r; -} - -int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) { - _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL; - DnsZoneItem *existing; - int r; - - assert(z); - assert(s); - assert(rr); - - if (dns_class_is_pseudo(rr->key->class)) - return -EINVAL; - if (dns_type_is_pseudo(rr->key->type)) - return -EINVAL; - - existing = dns_zone_get(z, rr); - if (existing) - return 0; - - r = dns_zone_init(z); - if (r < 0) - return r; - - i = new0(DnsZoneItem, 1); - if (!i) - return -ENOMEM; - - i->scope = s; - i->rr = dns_resource_record_ref(rr); - i->probing_enabled = probe; - - r = dns_zone_link_item(z, i); - if (r < 0) - return r; - - if (probe) { - DnsZoneItem *first, *j; - bool established = false; - - /* Check if there's already an RR with the same name - * established. If so, it has been probed already, and - * we don't ned to probe again. */ - - LIST_FIND_HEAD(by_name, i, first); - LIST_FOREACH(by_name, j, first) { - if (i == j) - continue; - - if (j->state == DNS_ZONE_ITEM_ESTABLISHED) - established = true; - } - - if (established) - i->state = DNS_ZONE_ITEM_ESTABLISHED; - else { - i->state = DNS_ZONE_ITEM_PROBING; - - r = dns_zone_item_probe_start(i); - if (r < 0) { - dns_zone_item_remove_and_free(z, i); - i = NULL; - return r; - } - } - } else - i->state = DNS_ZONE_ITEM_ESTABLISHED; - - i = NULL; - return 0; -} - -int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, int ifindex, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) { - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL; - unsigned n_answer = 0; - DnsZoneItem *j, *first; - bool tentative = true, need_soa = false; - int r; - - /* Note that we don't actually need the ifindex for anything. However when it is passed we'll initialize the - * ifindex field in the answer with it */ - - assert(z); - assert(key); - assert(ret_answer); - - /* First iteration, count what we have */ - - if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) { - bool found = false, added = false; - int k; - - /* If this is a generic match, then we have to - * go through the list by the name and look - * for everything manually */ - - first = hashmap_get(z->by_name, dns_resource_key_name(key)); - LIST_FOREACH(by_name, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - found = true; - - k = dns_resource_key_match_rr(key, j->rr, NULL); - if (k < 0) - return k; - if (k > 0) { - n_answer++; - added = true; - } - - } - - if (found && !added) - need_soa = true; - - } else { - bool found = false; - - /* If this is a specific match, then look for - * the right key immediately */ - - first = hashmap_get(z->by_key, key); - LIST_FOREACH(by_key, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - found = true; - n_answer++; - } - - if (!found) { - first = hashmap_get(z->by_name, dns_resource_key_name(key)); - LIST_FOREACH(by_name, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - need_soa = true; - break; - } - } - } - - if (n_answer <= 0 && !need_soa) - goto return_empty; - - if (n_answer > 0) { - answer = dns_answer_new(n_answer); - if (!answer) - return -ENOMEM; - } - - if (need_soa) { - soa = dns_answer_new(1); - if (!soa) - return -ENOMEM; - } - - /* Second iteration, actually add the RRs to the answers */ - if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) { - bool found = false, added = false; - int k; - - first = hashmap_get(z->by_name, dns_resource_key_name(key)); - LIST_FOREACH(by_name, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - found = true; - - if (j->state != DNS_ZONE_ITEM_PROBING) - tentative = false; - - k = dns_resource_key_match_rr(key, j->rr, NULL); - if (k < 0) - return k; - if (k > 0) { - r = dns_answer_add(answer, j->rr, ifindex, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - - added = true; - } - } - - if (found && !added) { - r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex); - if (r < 0) - return r; - } - } else { - bool found = false; - - first = hashmap_get(z->by_key, key); - LIST_FOREACH(by_key, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - found = true; - - if (j->state != DNS_ZONE_ITEM_PROBING) - tentative = false; - - r = dns_answer_add(answer, j->rr, ifindex, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - if (!found) { - bool add_soa = false; - - first = hashmap_get(z->by_name, dns_resource_key_name(key)); - LIST_FOREACH(by_name, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - if (j->state != DNS_ZONE_ITEM_PROBING) - tentative = false; - - add_soa = true; - } - - if (add_soa) { - r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex); - if (r < 0) - return r; - } - } - } - - /* If the caller sets ret_tentative to NULL, then use this as - * indication to not return tentative entries */ - - if (!ret_tentative && tentative) - goto return_empty; - - *ret_answer = answer; - answer = NULL; - - if (ret_soa) { - *ret_soa = soa; - soa = NULL; - } - - if (ret_tentative) - *ret_tentative = tentative; - - return 1; - -return_empty: - *ret_answer = NULL; - - if (ret_soa) - *ret_soa = NULL; - - if (ret_tentative) - *ret_tentative = false; - - return 0; -} - -void dns_zone_item_conflict(DnsZoneItem *i) { - assert(i); - - if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED)) - return; - - log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr))); - - dns_zone_item_probe_stop(i); - - /* Withdraw the conflict item */ - i->state = DNS_ZONE_ITEM_WITHDRAWN; - - /* Maybe change the hostname */ - if (manager_is_own_hostname(i->scope->manager, dns_resource_key_name(i->rr->key)) > 0) - manager_next_hostname(i->scope->manager); -} - -void dns_zone_item_notify(DnsZoneItem *i) { - assert(i); - assert(i->probe_transaction); - - if (i->block_ready > 0) - return; - - if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING)) - return; - - if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) { - bool we_lost = false; - - /* The probe got a successful reply. If we so far - * weren't established we just give up. If we already - * were established, and the peer has the - * lexicographically larger IP address we continue - * and defend it. */ - - if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) { - log_debug("Got a successful probe for not yet established RR, we lost."); - we_lost = true; - } else { - assert(i->probe_transaction->received); - we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0; - if (we_lost) - log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost."); - } - - if (we_lost) { - dns_zone_item_conflict(i); - return; - } - - log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost."); - } - - log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i->rr))); - - dns_zone_item_probe_stop(i); - i->state = DNS_ZONE_ITEM_ESTABLISHED; -} - -static int dns_zone_item_verify(DnsZoneItem *i) { - int r; - - assert(i); - - if (i->state != DNS_ZONE_ITEM_ESTABLISHED) - return 0; - - log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i->rr))); - - i->state = DNS_ZONE_ITEM_VERIFYING; - r = dns_zone_item_probe_start(i); - if (r < 0) { - log_error_errno(r, "Failed to start probing for verifying RR: %m"); - i->state = DNS_ZONE_ITEM_ESTABLISHED; - return r; - } - - return 0; -} - -int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) { - DnsZoneItem *i, *first; - int c = 0; - - assert(zone); - assert(rr); - - /* This checks whether a response RR we received from somebody - * else is one that we actually thought was uniquely ours. If - * so, we'll verify our RRs. */ - - /* No conflict if we don't have the name at all. */ - first = hashmap_get(zone->by_name, dns_resource_key_name(rr->key)); - if (!first) - return 0; - - /* No conflict if we have the exact same RR */ - if (dns_zone_get(zone, rr)) - return 0; - - /* OK, somebody else has RRs for the same name. Yuck! Let's - * start probing again */ - - LIST_FOREACH(by_name, i, first) { - if (dns_resource_record_equal(i->rr, rr)) - continue; - - dns_zone_item_verify(i); - c++; - } - - return c; -} - -int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) { - DnsZoneItem *i, *first; - int c = 0; - - assert(zone); - - /* Somebody else notified us about a possible conflict. Let's - * verify if that's true. */ - - first = hashmap_get(zone->by_name, dns_resource_key_name(key)); - if (!first) - return 0; - - LIST_FOREACH(by_name, i, first) { - dns_zone_item_verify(i); - c++; - } - - return c; -} - -void dns_zone_verify_all(DnsZone *zone) { - DnsZoneItem *i; - Iterator iterator; - - assert(zone); - - HASHMAP_FOREACH(i, zone->by_key, iterator) { - DnsZoneItem *j; - - LIST_FOREACH(by_key, j, i) - dns_zone_item_verify(j); - } -} - -void dns_zone_dump(DnsZone *zone, FILE *f) { - Iterator iterator; - DnsZoneItem *i; - - if (!zone) - return; - - if (!f) - f = stdout; - - HASHMAP_FOREACH(i, zone->by_key, iterator) { - DnsZoneItem *j; - - LIST_FOREACH(by_key, j, i) { - const char *t; - - t = dns_resource_record_to_string(j->rr); - if (!t) { - log_oom(); - continue; - } - - fputc('\t', f); - fputs(t, f); - fputc('\n', f); - } - } -} - -bool dns_zone_is_empty(DnsZone *zone) { - if (!zone) - return true; - - return hashmap_isempty(zone->by_key); -} diff --git a/src/resolve/resolved-dns-zone.h b/src/resolve/resolved-dns-zone.h deleted file mode 100644 index a41df37e6b..0000000000 --- a/src/resolve/resolved-dns-zone.h +++ /dev/null @@ -1,81 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include "hashmap.h" - -typedef struct DnsZone { - Hashmap *by_key; - Hashmap *by_name; -} DnsZone; - -typedef struct DnsZoneItem DnsZoneItem; -typedef enum DnsZoneItemState DnsZoneItemState; - -#include "resolved-dns-answer.h" -#include "resolved-dns-question.h" -#include "resolved-dns-rr.h" -#include "resolved-dns-transaction.h" - -/* RFC 4795 Section 2.8. suggests a TTL of 30s by default */ -#define LLMNR_DEFAULT_TTL (30) - -enum DnsZoneItemState { - DNS_ZONE_ITEM_PROBING, - DNS_ZONE_ITEM_ESTABLISHED, - DNS_ZONE_ITEM_VERIFYING, - DNS_ZONE_ITEM_WITHDRAWN, -}; - -struct DnsZoneItem { - DnsScope *scope; - DnsResourceRecord *rr; - - DnsZoneItemState state; - - unsigned block_ready; - - bool probing_enabled; - - LIST_FIELDS(DnsZoneItem, by_key); - LIST_FIELDS(DnsZoneItem, by_name); - - DnsTransaction *probe_transaction; -}; - -void dns_zone_flush(DnsZone *z); - -int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe); -void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr); - -int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, int ifindex, DnsAnswer **answer, DnsAnswer **soa, bool *tentative); - -void dns_zone_item_conflict(DnsZoneItem *i); -void dns_zone_item_notify(DnsZoneItem *i); - -int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr); -int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key); - -void dns_zone_verify_all(DnsZone *zone); - -void dns_zone_item_probe_stop(DnsZoneItem *i); - -void dns_zone_dump(DnsZone *zone, FILE *f); -bool dns_zone_is_empty(DnsZone *zone); diff --git a/src/resolve/resolved-etc-hosts.c b/src/resolve/resolved-etc-hosts.c deleted file mode 100644 index 40d650949d..0000000000 --- a/src/resolve/resolved-etc-hosts.c +++ /dev/null @@ -1,448 +0,0 @@ -/*** - 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 != y->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 deleted file mode 100644 index 9d5a175f18..0000000000 --- a/src/resolve/resolved-etc-hosts.h +++ /dev/null @@ -1,28 +0,0 @@ -#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-gperf.gperf b/src/resolve/resolved-gperf.gperf deleted file mode 100644 index 446f85cdf4..0000000000 --- a/src/resolve/resolved-gperf.gperf +++ /dev/null @@ -1,23 +0,0 @@ -%{ -#include <stddef.h> -#include "conf-parser.h" -#include "resolved-conf.h" -%} -struct ConfigPerfItem; -%null_strings -%language=ANSI-C -%define slot-name section_and_lvalue -%define hash-function-name resolved_gperf_hash -%define lookup-function-name resolved_gperf_lookup -%readonly-tables -%omit-struct-type -%struct-type -%includes -%% -Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0 -Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0 -Resolve.Domains, config_parse_search_domains, 0, 0 -Resolve.LLMNR, config_parse_resolve_support, 0, offsetof(Manager, llmnr_support) -Resolve.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Manager, dnssec_mode) -Resolve.Cache, config_parse_bool, 0, offsetof(Manager, enable_cache) -Resolve.DNSStubListener, config_parse_dns_stub_listener_mode, 0, offsetof(Manager, dns_stub_listener_mode) diff --git a/src/resolve/resolved-link-bus.c b/src/resolve/resolved-link-bus.c deleted file mode 100644 index 364812250f..0000000000 --- a/src/resolve/resolved-link-bus.c +++ /dev/null @@ -1,629 +0,0 @@ -/*** - 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 "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-util.h" -#include "parse-util.h" -#include "resolve-util.h" -#include "resolved-bus.h" -#include "resolved-link-bus.h" -#include "resolved-resolv-conf.h" -#include "strv.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_resolve_support, resolve_support, ResolveSupport); - -static int property_get_dnssec_mode( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Link *l = userdata; - - assert(reply); - assert(l); - - return sd_bus_message_append(reply, "s", dnssec_mode_to_string(link_get_dnssec_mode(l))); -} - -static int property_get_dns( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Link *l = userdata; - DnsServer *s; - int r; - - assert(reply); - assert(l); - - r = sd_bus_message_open_container(reply, 'a', "(iay)"); - if (r < 0) - return r; - - LIST_FOREACH(servers, s, l->dns_servers) { - r = bus_dns_server_append(reply, s, false); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_domains( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Link *l = userdata; - DnsSearchDomain *d; - int r; - - assert(reply); - assert(l); - - r = sd_bus_message_open_container(reply, 'a', "(sb)"); - if (r < 0) - return r; - - LIST_FOREACH(domains, d, l->search_domains) { - r = sd_bus_message_append(reply, "(sb)", d->name, d->route_only); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_scopes_mask( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Link *l = userdata; - uint64_t mask; - - assert(reply); - assert(l); - - mask = (l->unicast_scope ? SD_RESOLVED_DNS : 0) | - (l->llmnr_ipv4_scope ? SD_RESOLVED_LLMNR_IPV4 : 0) | - (l->llmnr_ipv6_scope ? SD_RESOLVED_LLMNR_IPV6 : 0) | - (l->mdns_ipv4_scope ? SD_RESOLVED_MDNS_IPV4 : 0) | - (l->mdns_ipv6_scope ? SD_RESOLVED_MDNS_IPV6 : 0); - - return sd_bus_message_append(reply, "t", mask); -} - -static int property_get_ntas( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Link *l = userdata; - const char *name; - Iterator i; - int r; - - assert(reply); - assert(l); - - r = sd_bus_message_open_container(reply, 'a', "s"); - if (r < 0) - return r; - - SET_FOREACH(name, l->dnssec_negative_trust_anchors, i) { - r = sd_bus_message_append(reply, "s", name); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_dnssec_supported( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Link *l = userdata; - - assert(reply); - assert(l); - - return sd_bus_message_append(reply, "b", link_dnssec_supported(l)); -} - -static int verify_unmanaged_link(Link *l, sd_bus_error *error) { - assert(l); - - if (l->flags & IFF_LOOPBACK) - return sd_bus_error_setf(error, BUS_ERROR_LINK_BUSY, "Link %s is loopback device.", l->name); - if (l->is_managed) - return sd_bus_error_setf(error, BUS_ERROR_LINK_BUSY, "Link %s is managed.", l->name); - - return 0; -} - -int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ struct in_addr_data *dns = NULL; - size_t allocated = 0, n = 0; - Link *l = userdata; - unsigned i; - int r; - - assert(message); - assert(l); - - r = verify_unmanaged_link(l, error); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(message, 'a', "(iay)"); - if (r < 0) - return r; - - for (;;) { - int family; - size_t sz; - const void *d; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_enter_container(message, 'r', "iay"); - if (r < 0) - return r; - if (r == 0) - break; - - r = sd_bus_message_read(message, "i", &family); - if (r < 0) - return r; - - if (!IN_SET(family, AF_INET, AF_INET6)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); - - r = sd_bus_message_read_array(message, 'y', &d, &sz); - if (r < 0) - return r; - if (sz != FAMILY_ADDRESS_SIZE(family)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid address size"); - - if (!dns_server_address_valid(family, d)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid DNS server address"); - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (!GREEDY_REALLOC(dns, allocated, n+1)) - return -ENOMEM; - - dns[n].family = family; - memcpy(&dns[n].address, d, sz); - n++; - } - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - dns_server_mark_all(l->dns_servers); - - for (i = 0; i < n; i++) { - DnsServer *s; - - s = dns_server_find(l->dns_servers, dns[i].family, &dns[i].address, 0); - if (s) - dns_server_move_back_and_unmark(s); - else { - r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, dns[i].family, &dns[i].address, 0); - if (r < 0) - goto clear; - } - - } - - dns_server_unlink_marked(l->dns_servers); - link_allocate_scopes(l); - - (void) link_save_user(l); - (void) manager_write_resolv_conf(l->manager); - - return sd_bus_reply_method_return(message, NULL); - -clear: - dns_server_unlink_all(l->dns_servers); - return r; -} - -int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Link *l = userdata; - int r; - - assert(message); - assert(l); - - r = verify_unmanaged_link(l, error); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(message, 'a', "(sb)"); - if (r < 0) - return r; - - for (;;) { - const char *name; - int route_only; - - r = sd_bus_message_read(message, "(sb)", &name, &route_only); - if (r < 0) - return r; - if (r == 0) - break; - - r = dns_name_is_valid(name); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid search domain %s", name); - if (!route_only && dns_name_is_root(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Root domain is not suitable as search domain"); - } - - dns_search_domain_mark_all(l->search_domains); - - r = sd_bus_message_rewind(message, false); - if (r < 0) - return r; - - for (;;) { - DnsSearchDomain *d; - const char *name; - int route_only; - - r = sd_bus_message_read(message, "(sb)", &name, &route_only); - if (r < 0) - goto clear; - if (r == 0) - break; - - r = dns_search_domain_find(l->search_domains, name, &d); - if (r < 0) - goto clear; - - if (r > 0) - dns_search_domain_move_back_and_unmark(d); - else { - r = dns_search_domain_new(l->manager, &d, DNS_SEARCH_DOMAIN_LINK, l, name); - if (r < 0) - goto clear; - } - - d->route_only = route_only; - } - - r = sd_bus_message_exit_container(message); - if (r < 0) - goto clear; - - dns_search_domain_unlink_marked(l->search_domains); - - (void) link_save_user(l); - (void) manager_write_resolv_conf(l->manager); - - return sd_bus_reply_method_return(message, NULL); - -clear: - dns_search_domain_unlink_all(l->search_domains); - return r; -} - -int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Link *l = userdata; - ResolveSupport mode; - const char *llmnr; - int r; - - assert(message); - assert(l); - - r = verify_unmanaged_link(l, error); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "s", &llmnr); - if (r < 0) - return r; - - if (isempty(llmnr)) - mode = RESOLVE_SUPPORT_YES; - else { - mode = resolve_support_from_string(llmnr); - if (mode < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid LLMNR setting: %s", llmnr); - } - - l->llmnr_support = mode; - link_allocate_scopes(l); - link_add_rrs(l, false); - - (void) link_save_user(l); - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Link *l = userdata; - ResolveSupport mode; - const char *mdns; - int r; - - assert(message); - assert(l); - - r = verify_unmanaged_link(l, error); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "s", &mdns); - if (r < 0) - return r; - - if (isempty(mdns)) - mode = RESOLVE_SUPPORT_NO; - else { - mode = resolve_support_from_string(mdns); - if (mode < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid MulticastDNS setting: %s", mdns); - } - - l->mdns_support = mode; - link_allocate_scopes(l); - link_add_rrs(l, false); - - (void) link_save_user(l); - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Link *l = userdata; - const char *dnssec; - DnssecMode mode; - int r; - - assert(message); - assert(l); - - r = verify_unmanaged_link(l, error); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "s", &dnssec); - if (r < 0) - return r; - - if (isempty(dnssec)) - mode = _DNSSEC_MODE_INVALID; - else { - mode = dnssec_mode_from_string(dnssec); - if (mode < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid DNSSEC setting: %s", dnssec); - } - - link_set_dnssec_mode(l, mode); - - (void) link_save_user(l); - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_set_free_free_ Set *ns = NULL; - _cleanup_free_ char **ntas = NULL; - Link *l = userdata; - int r; - char **i; - - assert(message); - assert(l); - - r = verify_unmanaged_link(l, error); - if (r < 0) - return r; - - r = sd_bus_message_read_strv(message, &ntas); - if (r < 0) - return r; - - STRV_FOREACH(i, ntas) { - r = dns_name_is_valid(*i); - if (r < 0) - return r; - if (r == 0) - 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); - if (!ns) - return -ENOMEM; - - STRV_FOREACH(i, ntas) { - r = set_put_strdup(ns, *i); - if (r < 0) - return r; - } - - set_free_free(l->dnssec_negative_trust_anchors); - l->dnssec_negative_trust_anchors = ns; - ns = NULL; - - (void) link_save_user(l); - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_link_method_revert(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Link *l = userdata; - int r; - - assert(message); - assert(l); - - r = verify_unmanaged_link(l, error); - if (r < 0) - return r; - - link_flush_settings(l); - link_allocate_scopes(l); - link_add_rrs(l, false); - - (void) link_save_user(l); - (void) manager_write_resolv_conf(l->manager); - - return sd_bus_reply_method_return(message, NULL); -} - -const sd_bus_vtable link_vtable[] = { - SD_BUS_VTABLE_START(0), - - SD_BUS_PROPERTY("ScopesMask", "t", property_get_scopes_mask, 0, 0), - SD_BUS_PROPERTY("DNS", "a(iay)", property_get_dns, 0, 0), - SD_BUS_PROPERTY("Domains", "a(sb)", property_get_domains, 0, 0), - SD_BUS_PROPERTY("LLMNR", "s", property_get_resolve_support, offsetof(Link, llmnr_support), 0), - SD_BUS_PROPERTY("MulticastDNS", "s", property_get_resolve_support, offsetof(Link, mdns_support), 0), - SD_BUS_PROPERTY("DNSSEC", "s", property_get_dnssec_mode, 0, 0), - SD_BUS_PROPERTY("DNSSECNegativeTrustAnchors", "as", property_get_ntas, 0, 0), - SD_BUS_PROPERTY("DNSSECSupported", "b", property_get_dnssec_supported, 0, 0), - - SD_BUS_METHOD("SetDNS", "a(iay)", NULL, bus_link_method_set_dns_servers, 0), - SD_BUS_METHOD("SetDomains", "a(sb)", NULL, bus_link_method_set_domains, 0), - SD_BUS_METHOD("SetLLMNR", "s", NULL, bus_link_method_set_llmnr, 0), - SD_BUS_METHOD("SetMulticastDNS", "s", NULL, bus_link_method_set_mdns, 0), - SD_BUS_METHOD("SetDNSSEC", "s", NULL, bus_link_method_set_dnssec, 0), - SD_BUS_METHOD("SetDNSSECNegativeTrustAnchors", "as", NULL, bus_link_method_set_dnssec_negative_trust_anchors, 0), - SD_BUS_METHOD("Revert", NULL, NULL, bus_link_method_revert, 0), - - SD_BUS_VTABLE_END -}; - -int link_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - _cleanup_free_ char *e = NULL; - Manager *m = userdata; - int ifindex; - Link *link; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - assert(m); - - r = sd_bus_path_decode(path, "/org/freedesktop/resolve1/link", &e); - if (r <= 0) - return 0; - - r = parse_ifindex(e, &ifindex); - if (r < 0) - return 0; - - link = hashmap_get(m->links, INT_TO_PTR(ifindex)); - if (!link) - return 0; - - *found = link; - return 1; -} - -char *link_bus_path(Link *link) { - _cleanup_free_ char *ifindex = NULL; - char *p; - int r; - - assert(link); - - if (asprintf(&ifindex, "%i", link->ifindex) < 0) - return NULL; - - r = sd_bus_path_encode("/org/freedesktop/resolve1/link", ifindex, &p); - if (r < 0) - return NULL; - - return p; -} - -int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { - _cleanup_strv_free_ char **l = NULL; - Manager *m = userdata; - Link *link; - Iterator i; - unsigned c = 0; - - assert(bus); - assert(path); - assert(m); - assert(nodes); - - l = new0(char*, hashmap_size(m->links) + 1); - if (!l) - return -ENOMEM; - - HASHMAP_FOREACH(link, m->links, i) { - char *p; - - p = link_bus_path(link); - if (!p) - return -ENOMEM; - - l[c++] = p; - } - - l[c] = NULL; - *nodes = l; - l = NULL; - - return 1; -} diff --git a/src/resolve/resolved-link-bus.h b/src/resolve/resolved-link-bus.h deleted file mode 100644 index 646031b631..0000000000 --- a/src/resolve/resolved-link-bus.h +++ /dev/null @@ -1,38 +0,0 @@ -#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 "sd-bus.h" - -#include "resolved-link.h" - -extern const sd_bus_vtable link_vtable[]; - -int link_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error); -char *link_bus_path(Link *link); -int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error); - -int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_link_method_revert(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c deleted file mode 100644 index 13e1f91192..0000000000 --- a/src/resolve/resolved-link.c +++ /dev/null @@ -1,1113 +0,0 @@ -/*** - 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 <net/if.h> - -#include "sd-network.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "missing.h" -#include "mkdir.h" -#include "parse-util.h" -#include "resolved-link.h" -#include "string-util.h" -#include "strv.h" - -int link_new(Manager *m, Link **ret, int ifindex) { - _cleanup_(link_freep) Link *l = NULL; - int r; - - assert(m); - assert(ifindex > 0); - - r = hashmap_ensure_allocated(&m->links, NULL); - if (r < 0) - return r; - - l = new0(Link, 1); - if (!l) - return -ENOMEM; - - l->ifindex = ifindex; - l->llmnr_support = RESOLVE_SUPPORT_YES; - l->mdns_support = RESOLVE_SUPPORT_NO; - l->dnssec_mode = _DNSSEC_MODE_INVALID; - l->operstate = IF_OPER_UNKNOWN; - - if (asprintf(&l->state_file, "/run/systemd/resolve/netif/%i", ifindex) < 0) - return -ENOMEM; - - r = hashmap_put(m->links, INT_TO_PTR(ifindex), l); - if (r < 0) - return r; - - l->manager = m; - - if (ret) - *ret = l; - l = NULL; - - return 0; -} - -void link_flush_settings(Link *l) { - assert(l); - - l->llmnr_support = RESOLVE_SUPPORT_YES; - l->mdns_support = RESOLVE_SUPPORT_NO; - l->dnssec_mode = _DNSSEC_MODE_INVALID; - - dns_server_unlink_all(l->dns_servers); - dns_search_domain_unlink_all(l->search_domains); - - l->dnssec_negative_trust_anchors = set_free_free(l->dnssec_negative_trust_anchors); -} - -Link *link_free(Link *l) { - if (!l) - return NULL; - - link_flush_settings(l); - - while (l->addresses) - (void) link_address_free(l->addresses); - - if (l->manager) - hashmap_remove(l->manager->links, INT_TO_PTR(l->ifindex)); - - dns_scope_free(l->unicast_scope); - dns_scope_free(l->llmnr_ipv4_scope); - dns_scope_free(l->llmnr_ipv6_scope); - dns_scope_free(l->mdns_ipv4_scope); - dns_scope_free(l->mdns_ipv6_scope); - - free(l->state_file); - - return mfree(l); -} - -void link_allocate_scopes(Link *l) { - int r; - - assert(l); - - if (link_relevant(l, AF_UNSPEC, false) && - l->dns_servers) { - if (!l->unicast_scope) { - r = dns_scope_new(l->manager, &l->unicast_scope, l, DNS_PROTOCOL_DNS, AF_UNSPEC); - if (r < 0) - log_warning_errno(r, "Failed to allocate DNS scope: %m"); - } - } else - l->unicast_scope = dns_scope_free(l->unicast_scope); - - if (link_relevant(l, AF_INET, true) && - l->llmnr_support != RESOLVE_SUPPORT_NO && - l->manager->llmnr_support != RESOLVE_SUPPORT_NO) { - if (!l->llmnr_ipv4_scope) { - r = dns_scope_new(l->manager, &l->llmnr_ipv4_scope, l, DNS_PROTOCOL_LLMNR, AF_INET); - if (r < 0) - log_warning_errno(r, "Failed to allocate LLMNR IPv4 scope: %m"); - } - } else - l->llmnr_ipv4_scope = dns_scope_free(l->llmnr_ipv4_scope); - - if (link_relevant(l, AF_INET6, true) && - l->llmnr_support != RESOLVE_SUPPORT_NO && - l->manager->llmnr_support != RESOLVE_SUPPORT_NO && - socket_ipv6_is_supported()) { - if (!l->llmnr_ipv6_scope) { - r = dns_scope_new(l->manager, &l->llmnr_ipv6_scope, l, DNS_PROTOCOL_LLMNR, AF_INET6); - if (r < 0) - log_warning_errno(r, "Failed to allocate LLMNR IPv6 scope: %m"); - } - } else - l->llmnr_ipv6_scope = dns_scope_free(l->llmnr_ipv6_scope); - - if (link_relevant(l, AF_INET, true) && - l->mdns_support != RESOLVE_SUPPORT_NO && - l->manager->mdns_support != RESOLVE_SUPPORT_NO) { - if (!l->mdns_ipv4_scope) { - r = dns_scope_new(l->manager, &l->mdns_ipv4_scope, l, DNS_PROTOCOL_MDNS, AF_INET); - if (r < 0) - log_warning_errno(r, "Failed to allocate mDNS IPv4 scope: %m"); - } - } else - l->mdns_ipv4_scope = dns_scope_free(l->mdns_ipv4_scope); - - if (link_relevant(l, AF_INET6, true) && - l->mdns_support != RESOLVE_SUPPORT_NO && - l->manager->mdns_support != RESOLVE_SUPPORT_NO) { - if (!l->mdns_ipv6_scope) { - r = dns_scope_new(l->manager, &l->mdns_ipv6_scope, l, DNS_PROTOCOL_MDNS, AF_INET6); - if (r < 0) - log_warning_errno(r, "Failed to allocate mDNS IPv6 scope: %m"); - } - } else - l->mdns_ipv6_scope = dns_scope_free(l->mdns_ipv6_scope); -} - -void link_add_rrs(Link *l, bool force_remove) { - LinkAddress *a; - - LIST_FOREACH(addresses, a, l->addresses) - link_address_add_rrs(a, force_remove); -} - -int link_process_rtnl(Link *l, sd_netlink_message *m) { - const char *n = NULL; - int r; - - assert(l); - assert(m); - - r = sd_rtnl_message_link_get_flags(m, &l->flags); - if (r < 0) - return r; - - (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); - char_array_0(l->name); - } - - link_allocate_scopes(l); - link_add_rrs(l, false); - - return 0; -} - -static int link_update_dns_server_one(Link *l, const char *name) { - union in_addr_union a; - DnsServer *s; - int family, r; - - assert(l); - assert(name); - - r = in_addr_from_string_auto(name, &family, &a); - if (r < 0) - return r; - - s = dns_server_find(l->dns_servers, family, &a, 0); - if (s) { - dns_server_move_back_and_unmark(s); - return 0; - } - - return dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, family, &a, 0); -} - -static int link_update_dns_servers(Link *l) { - _cleanup_strv_free_ char **nameservers = NULL; - char **nameserver; - int r; - - assert(l); - - r = sd_network_link_get_dns(l->ifindex, &nameservers); - if (r == -ENODATA) { - r = 0; - goto clear; - } - if (r < 0) - goto clear; - - dns_server_mark_all(l->dns_servers); - - STRV_FOREACH(nameserver, nameservers) { - r = link_update_dns_server_one(l, *nameserver); - if (r < 0) - goto clear; - } - - dns_server_unlink_marked(l->dns_servers); - return 0; - -clear: - dns_server_unlink_all(l->dns_servers); - return r; -} - -static int link_update_llmnr_support(Link *l) { - _cleanup_free_ char *b = NULL; - int r; - - assert(l); - - r = sd_network_link_get_llmnr(l->ifindex, &b); - if (r == -ENODATA) { - r = 0; - goto clear; - } - if (r < 0) - goto clear; - - l->llmnr_support = resolve_support_from_string(b); - if (l->llmnr_support < 0) { - r = -EINVAL; - goto clear; - } - - return 0; - -clear: - l->llmnr_support = RESOLVE_SUPPORT_YES; - return r; -} - -static int link_update_mdns_support(Link *l) { - _cleanup_free_ char *b = NULL; - int r; - - assert(l); - - r = sd_network_link_get_mdns(l->ifindex, &b); - if (r == -ENODATA) { - r = 0; - goto clear; - } - if (r < 0) - goto clear; - - l->mdns_support = resolve_support_from_string(b); - if (l->mdns_support < 0) { - r = -EINVAL; - goto clear; - } - - return 0; - -clear: - l->mdns_support = RESOLVE_SUPPORT_NO; - return r; -} - -void link_set_dnssec_mode(Link *l, DnssecMode mode) { - - assert(l); - - if (l->dnssec_mode == mode) - return; - - if ((l->dnssec_mode == _DNSSEC_MODE_INVALID) || - (l->dnssec_mode == DNSSEC_NO && mode != DNSSEC_NO) || - (l->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE && mode == DNSSEC_YES)) { - - /* When switching from non-DNSSEC mode to DNSSEC mode, flush the cache. Also when switching from the - * allow-downgrade mode to full DNSSEC mode, flush it too. */ - if (l->unicast_scope) - dns_cache_flush(&l->unicast_scope->cache); - } - - l->dnssec_mode = mode; -} - -static int link_update_dnssec_mode(Link *l) { - _cleanup_free_ char *m = NULL; - DnssecMode mode; - int r; - - assert(l); - - r = sd_network_link_get_dnssec(l->ifindex, &m); - if (r == -ENODATA) { - r = 0; - goto clear; - } - if (r < 0) - goto clear; - - mode = dnssec_mode_from_string(m); - if (mode < 0) { - r = -EINVAL; - goto clear; - } - - link_set_dnssec_mode(l, mode); - - return 0; - -clear: - l->dnssec_mode = _DNSSEC_MODE_INVALID; - return r; -} - -static int link_update_dnssec_negative_trust_anchors(Link *l) { - _cleanup_strv_free_ char **ntas = NULL; - _cleanup_set_free_free_ Set *ns = NULL; - int r; - - assert(l); - - r = sd_network_link_get_dnssec_negative_trust_anchors(l->ifindex, &ntas); - if (r == -ENODATA) { - r = 0; - goto clear; - } - if (r < 0) - goto clear; - - ns = set_new(&dns_name_hash_ops); - if (!ns) - return -ENOMEM; - - r = set_put_strdupv(ns, ntas); - if (r < 0) - return r; - - set_free_free(l->dnssec_negative_trust_anchors); - l->dnssec_negative_trust_anchors = ns; - ns = NULL; - - return 0; - -clear: - l->dnssec_negative_trust_anchors = set_free_free(l->dnssec_negative_trust_anchors); - return r; -} - -static int link_update_search_domain_one(Link *l, const char *name, bool route_only) { - DnsSearchDomain *d; - int r; - - assert(l); - assert(name); - - r = dns_search_domain_find(l->search_domains, name, &d); - if (r < 0) - return r; - if (r > 0) - dns_search_domain_move_back_and_unmark(d); - else { - r = dns_search_domain_new(l->manager, &d, DNS_SEARCH_DOMAIN_LINK, l, name); - if (r < 0) - return r; - } - - d->route_only = route_only; - return 0; -} - -static int link_update_search_domains(Link *l) { - _cleanup_strv_free_ char **sdomains = NULL, **rdomains = NULL; - char **i; - int r, q; - - assert(l); - - r = sd_network_link_get_search_domains(l->ifindex, &sdomains); - if (r < 0 && r != -ENODATA) - goto clear; - - q = sd_network_link_get_route_domains(l->ifindex, &rdomains); - if (q < 0 && q != -ENODATA) { - r = q; - goto clear; - } - - if (r == -ENODATA && q == -ENODATA) { - /* networkd knows nothing about this interface, and that's fine. */ - r = 0; - goto clear; - } - - dns_search_domain_mark_all(l->search_domains); - - STRV_FOREACH(i, sdomains) { - r = link_update_search_domain_one(l, *i, false); - if (r < 0) - goto clear; - } - - STRV_FOREACH(i, rdomains) { - r = link_update_search_domain_one(l, *i, true); - if (r < 0) - goto clear; - } - - dns_search_domain_unlink_marked(l->search_domains); - return 0; - -clear: - dns_search_domain_unlink_all(l->search_domains); - return r; -} - -static int link_is_managed(Link *l) { - _cleanup_free_ char *state = NULL; - int r; - - assert(l); - - r = sd_network_link_get_setup_state(l->ifindex, &state); - if (r == -ENODATA) - return 0; - if (r < 0) - return r; - - return !STR_IN_SET(state, "pending", "unmanaged"); -} - -static void link_read_settings(Link *l) { - int r; - - assert(l); - - /* Read settings from networkd, except when networkd is not managing this interface. */ - - r = link_is_managed(l); - if (r < 0) { - log_warning_errno(r, "Failed to determine whether interface %s is managed: %m", l->name); - return; - } - if (r == 0) { - - /* If this link used to be managed, but is now unmanaged, flush all our settings — but only once. */ - if (l->is_managed) - link_flush_settings(l); - - l->is_managed = false; - return; - } - - l->is_managed = true; - - r = link_update_dns_servers(l); - if (r < 0) - log_warning_errno(r, "Failed to read DNS servers for interface %s, ignoring: %m", l->name); - - r = link_update_llmnr_support(l); - if (r < 0) - log_warning_errno(r, "Failed to read LLMNR support for interface %s, ignoring: %m", l->name); - - r = link_update_mdns_support(l); - if (r < 0) - log_warning_errno(r, "Failed to read mDNS support for interface %s, ignoring: %m", l->name); - - r = link_update_dnssec_mode(l); - if (r < 0) - log_warning_errno(r, "Failed to read DNSSEC mode for interface %s, ignoring: %m", l->name); - - r = link_update_dnssec_negative_trust_anchors(l); - if (r < 0) - log_warning_errno(r, "Failed to read DNSSEC negative trust anchors for interface %s, ignoring: %m", l->name); - - r = link_update_search_domains(l); - if (r < 0) - log_warning_errno(r, "Failed to read search domains for interface %s, ignoring: %m", l->name); -} - -int link_update(Link *l) { - assert(l); - - link_read_settings(l); - link_load_user(l); - link_allocate_scopes(l); - link_add_rrs(l, false); - - return 0; -} - -bool link_relevant(Link *l, int family, bool local_multicast) { - _cleanup_free_ char *state = NULL; - LinkAddress *a; - - assert(l); - - /* A link is relevant for local multicast traffic if it isn't a loopback or pointopoint device, has a link - * beat, can do multicast and has at least one link-local (or better) IP address. - * - * A link is relevant for non-multicast traffic if it isn't a loopback device, has a link beat, and has at - * least one routable address.*/ - - if (l->flags & (IFF_LOOPBACK|IFF_DORMANT)) - return false; - - if ((l->flags & (IFF_UP|IFF_LOWER_UP)) != (IFF_UP|IFF_LOWER_UP)) - return false; - - if (local_multicast) { - if (l->flags & IFF_POINTOPOINT) - return false; - - if ((l->flags & IFF_MULTICAST) != IFF_MULTICAST) - return false; - } - - /* 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; - - LIST_FOREACH(addresses, a, l->addresses) - if ((family == AF_UNSPEC || a->family == family) && link_address_relevant(a, local_multicast)) - return true; - - return false; -} - -LinkAddress *link_find_address(Link *l, int family, const union in_addr_union *in_addr) { - LinkAddress *a; - - assert(l); - - LIST_FOREACH(addresses, a, l->addresses) - if (a->family == family && in_addr_equal(family, &a->in_addr, in_addr)) - return a; - - return NULL; -} - -DnsServer* link_set_dns_server(Link *l, DnsServer *s) { - assert(l); - - if (l->current_dns_server == s) - return s; - - if (s) - log_info("Switching to DNS server %s for interface %s.", dns_server_string(s), l->name); - - dns_server_unref(l->current_dns_server); - l->current_dns_server = dns_server_ref(s); - - if (l->unicast_scope) - dns_cache_flush(&l->unicast_scope->cache); - - return s; -} - -DnsServer *link_get_dns_server(Link *l) { - assert(l); - - if (!l->current_dns_server) - link_set_dns_server(l, l->dns_servers); - - return l->current_dns_server; -} - -void link_next_dns_server(Link *l) { - assert(l); - - if (!l->current_dns_server) - return; - - /* Change to the next one, but make sure to follow the linked - * list only if this server is actually still linked. */ - if (l->current_dns_server->linked && l->current_dns_server->servers_next) { - link_set_dns_server(l, l->current_dns_server->servers_next); - return; - } - - link_set_dns_server(l, l->dns_servers); -} - -DnssecMode link_get_dnssec_mode(Link *l) { - assert(l); - - if (l->dnssec_mode != _DNSSEC_MODE_INVALID) - return l->dnssec_mode; - - return manager_get_dnssec_mode(l->manager); -} - -bool link_dnssec_supported(Link *l) { - DnsServer *server; - - assert(l); - - if (link_get_dnssec_mode(l) == DNSSEC_NO) - return false; - - server = link_get_dns_server(l); - if (server) - return dns_server_dnssec_supported(server); - - return true; -} - -int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr) { - LinkAddress *a; - - assert(l); - assert(in_addr); - - a = new0(LinkAddress, 1); - if (!a) - return -ENOMEM; - - a->family = family; - a->in_addr = *in_addr; - - a->link = l; - LIST_PREPEND(addresses, l->addresses, a); - - if (ret) - *ret = a; - - return 0; -} - -LinkAddress *link_address_free(LinkAddress *a) { - if (!a) - return NULL; - - if (a->link) { - LIST_REMOVE(addresses, a->link->addresses, a); - - if (a->llmnr_address_rr) { - if (a->family == AF_INET && a->link->llmnr_ipv4_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_address_rr); - else if (a->family == AF_INET6 && a->link->llmnr_ipv6_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_address_rr); - } - - if (a->llmnr_ptr_rr) { - if (a->family == AF_INET && a->link->llmnr_ipv4_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_ptr_rr); - else if (a->family == AF_INET6 && a->link->llmnr_ipv6_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_ptr_rr); - } - } - - dns_resource_record_unref(a->llmnr_address_rr); - dns_resource_record_unref(a->llmnr_ptr_rr); - - return mfree(a); -} - -void link_address_add_rrs(LinkAddress *a, bool force_remove) { - int r; - - assert(a); - - if (a->family == AF_INET) { - - if (!force_remove && - link_address_relevant(a, true) && - a->link->llmnr_ipv4_scope && - a->link->llmnr_support == RESOLVE_SUPPORT_YES && - a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) { - - if (!a->link->manager->llmnr_host_ipv4_key) { - a->link->manager->llmnr_host_ipv4_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, a->link->manager->llmnr_hostname); - if (!a->link->manager->llmnr_host_ipv4_key) { - r = -ENOMEM; - goto fail; - } - } - - if (!a->llmnr_address_rr) { - a->llmnr_address_rr = dns_resource_record_new(a->link->manager->llmnr_host_ipv4_key); - if (!a->llmnr_address_rr) { - r = -ENOMEM; - goto fail; - } - - a->llmnr_address_rr->a.in_addr = a->in_addr.in; - a->llmnr_address_rr->ttl = LLMNR_DEFAULT_TTL; - } - - if (!a->llmnr_ptr_rr) { - r = dns_resource_record_new_reverse(&a->llmnr_ptr_rr, a->family, &a->in_addr, a->link->manager->llmnr_hostname); - if (r < 0) - goto fail; - - a->llmnr_ptr_rr->ttl = LLMNR_DEFAULT_TTL; - } - - r = dns_zone_put(&a->link->llmnr_ipv4_scope->zone, a->link->llmnr_ipv4_scope, a->llmnr_address_rr, true); - if (r < 0) - log_warning_errno(r, "Failed to add A record to LLMNR zone: %m"); - - r = dns_zone_put(&a->link->llmnr_ipv4_scope->zone, a->link->llmnr_ipv4_scope, a->llmnr_ptr_rr, false); - if (r < 0) - log_warning_errno(r, "Failed to add IPv6 PTR record to LLMNR zone: %m"); - } else { - if (a->llmnr_address_rr) { - if (a->link->llmnr_ipv4_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_address_rr); - a->llmnr_address_rr = dns_resource_record_unref(a->llmnr_address_rr); - } - - if (a->llmnr_ptr_rr) { - if (a->link->llmnr_ipv4_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_ptr_rr); - a->llmnr_ptr_rr = dns_resource_record_unref(a->llmnr_ptr_rr); - } - } - } - - if (a->family == AF_INET6) { - - if (!force_remove && - link_address_relevant(a, true) && - a->link->llmnr_ipv6_scope && - a->link->llmnr_support == RESOLVE_SUPPORT_YES && - a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) { - - if (!a->link->manager->llmnr_host_ipv6_key) { - a->link->manager->llmnr_host_ipv6_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, a->link->manager->llmnr_hostname); - if (!a->link->manager->llmnr_host_ipv6_key) { - r = -ENOMEM; - goto fail; - } - } - - if (!a->llmnr_address_rr) { - a->llmnr_address_rr = dns_resource_record_new(a->link->manager->llmnr_host_ipv6_key); - if (!a->llmnr_address_rr) { - r = -ENOMEM; - goto fail; - } - - a->llmnr_address_rr->aaaa.in6_addr = a->in_addr.in6; - a->llmnr_address_rr->ttl = LLMNR_DEFAULT_TTL; - } - - if (!a->llmnr_ptr_rr) { - r = dns_resource_record_new_reverse(&a->llmnr_ptr_rr, a->family, &a->in_addr, a->link->manager->llmnr_hostname); - if (r < 0) - goto fail; - - a->llmnr_ptr_rr->ttl = LLMNR_DEFAULT_TTL; - } - - r = dns_zone_put(&a->link->llmnr_ipv6_scope->zone, a->link->llmnr_ipv6_scope, a->llmnr_address_rr, true); - if (r < 0) - log_warning_errno(r, "Failed to add AAAA record to LLMNR zone: %m"); - - r = dns_zone_put(&a->link->llmnr_ipv6_scope->zone, a->link->llmnr_ipv6_scope, a->llmnr_ptr_rr, false); - if (r < 0) - log_warning_errno(r, "Failed to add IPv6 PTR record to LLMNR zone: %m"); - } else { - if (a->llmnr_address_rr) { - if (a->link->llmnr_ipv6_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_address_rr); - a->llmnr_address_rr = dns_resource_record_unref(a->llmnr_address_rr); - } - - if (a->llmnr_ptr_rr) { - if (a->link->llmnr_ipv6_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_ptr_rr); - a->llmnr_ptr_rr = dns_resource_record_unref(a->llmnr_ptr_rr); - } - } - } - - return; - -fail: - log_debug_errno(r, "Failed to update address RRs: %m"); -} - -int link_address_update_rtnl(LinkAddress *a, sd_netlink_message *m) { - int r; - assert(a); - assert(m); - - r = sd_rtnl_message_addr_get_flags(m, &a->flags); - if (r < 0) - return r; - - sd_rtnl_message_addr_get_scope(m, &a->scope); - - link_allocate_scopes(a->link); - link_add_rrs(a->link, false); - - return 0; -} - -bool link_address_relevant(LinkAddress *a, bool local_multicast) { - assert(a); - - if (a->flags & (IFA_F_DEPRECATED|IFA_F_TENTATIVE)) - return false; - - if (a->scope >= (local_multicast ? RT_SCOPE_HOST : RT_SCOPE_LINK)) - return false; - - return true; -} - -static bool link_needs_save(Link *l) { - assert(l); - - /* Returns true if any of the settings where set different from the default */ - - if (l->is_managed) - return false; - - if (l->llmnr_support != RESOLVE_SUPPORT_YES || - l->mdns_support != RESOLVE_SUPPORT_NO || - l->dnssec_mode != _DNSSEC_MODE_INVALID) - return true; - - if (l->dns_servers || - l->search_domains) - return true; - - if (!set_isempty(l->dnssec_negative_trust_anchors)) - return true; - - return false; -} - -int link_save_user(Link *l) { - _cleanup_free_ char *temp_path = NULL; - _cleanup_fclose_ FILE *f = NULL; - const char *v; - int r; - - assert(l); - assert(l->state_file); - - if (!link_needs_save(l)) { - (void) unlink(l->state_file); - return 0; - } - - r = mkdir_parents(l->state_file, 0700); - if (r < 0) - goto fail; - - r = fopen_temporary(l->state_file, &f, &temp_path); - if (r < 0) - goto fail; - - fputs("# This is private data. Do not parse.\n", f); - - v = resolve_support_to_string(l->llmnr_support); - if (v) - fprintf(f, "LLMNR=%s\n", v); - - v = resolve_support_to_string(l->mdns_support); - if (v) - fprintf(f, "MDNS=%s\n", v); - - v = dnssec_mode_to_string(l->dnssec_mode); - if (v) - fprintf(f, "DNSSEC=%s\n", v); - - if (l->dns_servers) { - DnsServer *server; - - fputs("SERVERS=", f); - LIST_FOREACH(servers, server, l->dns_servers) { - - if (server != l->dns_servers) - fputc(' ', f); - - v = dns_server_string(server); - if (!v) { - r = -ENOMEM; - goto fail; - } - - fputs(v, f); - } - fputc('\n', f); - } - - if (l->search_domains) { - DnsSearchDomain *domain; - - fputs("DOMAINS=", f); - LIST_FOREACH(domains, domain, l->search_domains) { - - if (domain != l->search_domains) - fputc(' ', f); - - if (domain->route_only) - fputc('~', f); - - fputs(DNS_SEARCH_DOMAIN_NAME(domain), f); - } - fputc('\n', f); - } - - if (!set_isempty(l->dnssec_negative_trust_anchors)) { - bool space = false; - Iterator i; - char *nta; - - fputs("NTAS=", f); - SET_FOREACH(nta, l->dnssec_negative_trust_anchors, i) { - - if (space) - fputc(' ', f); - - fputs(nta, f); - space = true; - } - fputc('\n', f); - } - - r = fflush_and_check(f); - if (r < 0) - goto fail; - - if (rename(temp_path, l->state_file) < 0) { - r = -errno; - goto fail; - } - - return 0; - -fail: - (void) unlink(l->state_file); - - if (temp_path) - (void) unlink(temp_path); - - return log_error_errno(r, "Failed to save link data %s: %m", l->state_file); -} - -int link_load_user(Link *l) { - _cleanup_free_ char - *llmnr = NULL, - *mdns = NULL, - *dnssec = NULL, - *servers = NULL, - *domains = NULL, - *ntas = NULL; - - ResolveSupport s; - int r; - - assert(l); - assert(l->state_file); - - /* Try to load only a single time */ - if (l->loaded) - return 0; - l->loaded = true; - - if (l->is_managed) - return 0; /* if the device is managed, then networkd is our configuration source, not the bus API */ - - r = parse_env_file(l->state_file, NEWLINE, - "LLMNR", &llmnr, - "MDNS", &mdns, - "DNSSEC", &dnssec, - "SERVERS", &servers, - "DOMAINS", &domains, - "NTAS", &ntas, - NULL); - if (r == -ENOENT) - return 0; - if (r < 0) - goto fail; - - link_flush_settings(l); - - /* If we can't recognize the LLMNR or MDNS setting we don't override the default */ - s = resolve_support_from_string(llmnr); - if (s >= 0) - l->llmnr_support = s; - - s = resolve_support_from_string(mdns); - if (s >= 0) - l->mdns_support = s; - - /* If we can't recognize the DNSSEC setting, then set it to invalid, so that the daemon default is used. */ - l->dnssec_mode = dnssec_mode_from_string(dnssec); - - if (servers) { - const char *p = servers; - - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&p, &word, NULL, 0); - if (r < 0) - goto fail; - if (r == 0) - break; - - r = link_update_dns_server_one(l, word); - if (r < 0) { - log_debug_errno(r, "Failed to load DNS server '%s', ignoring: %m", word); - continue; - } - } - } - - if (domains) { - const char *p = domains; - - for (;;) { - _cleanup_free_ char *word = NULL; - const char *n; - bool is_route; - - r = extract_first_word(&p, &word, NULL, 0); - if (r < 0) - goto fail; - if (r == 0) - break; - - is_route = word[0] == '~'; - n = is_route ? word + 1 : word; - - r = link_update_search_domain_one(l, n, is_route); - if (r < 0) { - log_debug_errno(r, "Failed to load search domain '%s', ignoring: %m", word); - continue; - } - } - } - - if (ntas) { - _cleanup_set_free_free_ Set *ns = NULL; - - ns = set_new(&dns_name_hash_ops); - if (!ns) { - r = -ENOMEM; - goto fail; - } - - r = set_put_strsplit(ns, ntas, NULL, 0); - if (r < 0) - goto fail; - - l->dnssec_negative_trust_anchors = ns; - ns = NULL; - } - - return 0; - -fail: - return log_error_errno(r, "Failed to load link data %s: %m", l->state_file); -} - -void link_remove_user(Link *l) { - assert(l); - assert(l->state_file); - - (void) unlink(l->state_file); -} diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h deleted file mode 100644 index c9b2a58c34..0000000000 --- a/src/resolve/resolved-link.h +++ /dev/null @@ -1,119 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include <net/if.h> - -#include "in-addr-util.h" -#include "ratelimit.h" -#include "resolve-util.h" - -typedef struct Link Link; -typedef struct LinkAddress LinkAddress; - -#include "resolved-dns-rr.h" -#include "resolved-dns-scope.h" -#include "resolved-dns-search-domain.h" -#include "resolved-dns-server.h" -#include "resolved-manager.h" - -#define LINK_SEARCH_DOMAINS_MAX 32 -#define LINK_DNS_SERVERS_MAX 32 - -struct LinkAddress { - Link *link; - - int family; - union in_addr_union in_addr; - - unsigned char flags, scope; - - DnsResourceRecord *llmnr_address_rr; - DnsResourceRecord *llmnr_ptr_rr; - - LIST_FIELDS(LinkAddress, addresses); -}; - -struct Link { - Manager *manager; - - int ifindex; - unsigned flags; - - LIST_HEAD(LinkAddress, addresses); - - LIST_HEAD(DnsServer, dns_servers); - DnsServer *current_dns_server; - unsigned n_dns_servers; - - LIST_HEAD(DnsSearchDomain, search_domains); - unsigned n_search_domains; - - ResolveSupport llmnr_support; - ResolveSupport mdns_support; - DnssecMode dnssec_mode; - Set *dnssec_negative_trust_anchors; - - DnsScope *unicast_scope; - DnsScope *llmnr_ipv4_scope; - DnsScope *llmnr_ipv6_scope; - DnsScope *mdns_ipv4_scope; - DnsScope *mdns_ipv6_scope; - - bool is_managed; - - char name[IF_NAMESIZE]; - uint32_t mtu; - uint8_t operstate; - - bool loaded; - char *state_file; -}; - -int link_new(Manager *m, Link **ret, int ifindex); -Link *link_free(Link *l); -int link_process_rtnl(Link *l, sd_netlink_message *m); -int link_update(Link *l); -bool link_relevant(Link *l, int family, bool local_multicast); -LinkAddress* link_find_address(Link *l, int family, const union in_addr_union *in_addr); -void link_add_rrs(Link *l, bool force_remove); - -void link_flush_settings(Link *l); -void link_set_dnssec_mode(Link *l, DnssecMode mode); -void link_allocate_scopes(Link *l); - -DnsServer* link_set_dns_server(Link *l, DnsServer *s); -DnsServer* link_get_dns_server(Link *l); -void link_next_dns_server(Link *l); - -DnssecMode link_get_dnssec_mode(Link *l); -bool link_dnssec_supported(Link *l); - -int link_save_user(Link *l); -int link_load_user(Link *l); -void link_remove_user(Link *l); - -int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr); -LinkAddress *link_address_free(LinkAddress *a); -int link_address_update_rtnl(LinkAddress *a, sd_netlink_message *m); -bool link_address_relevant(LinkAddress *l, bool local_multicast); -void link_address_add_rrs(LinkAddress *a, bool force_remove); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_free); diff --git a/src/resolve/resolved-llmnr.c b/src/resolve/resolved-llmnr.c deleted file mode 100644 index 3516af58ee..0000000000 --- a/src/resolve/resolved-llmnr.c +++ /dev/null @@ -1,471 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen <teg@jklm.no> - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. - ***/ - -#include <netinet/in.h> -#include <resolv.h> - -#include "fd-util.h" -#include "resolved-llmnr.h" -#include "resolved-manager.h" - -void manager_llmnr_stop(Manager *m) { - assert(m); - - m->llmnr_ipv4_udp_event_source = sd_event_source_unref(m->llmnr_ipv4_udp_event_source); - m->llmnr_ipv4_udp_fd = safe_close(m->llmnr_ipv4_udp_fd); - - m->llmnr_ipv6_udp_event_source = sd_event_source_unref(m->llmnr_ipv6_udp_event_source); - m->llmnr_ipv6_udp_fd = safe_close(m->llmnr_ipv6_udp_fd); - - m->llmnr_ipv4_tcp_event_source = sd_event_source_unref(m->llmnr_ipv4_tcp_event_source); - m->llmnr_ipv4_tcp_fd = safe_close(m->llmnr_ipv4_tcp_fd); - - m->llmnr_ipv6_tcp_event_source = sd_event_source_unref(m->llmnr_ipv6_tcp_event_source); - m->llmnr_ipv6_tcp_fd = safe_close(m->llmnr_ipv6_tcp_fd); -} - -int manager_llmnr_start(Manager *m) { - int r; - - assert(m); - - if (m->llmnr_support == RESOLVE_SUPPORT_NO) - return 0; - - r = manager_llmnr_ipv4_udp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - - r = manager_llmnr_ipv4_tcp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - - if (socket_ipv6_is_supported()) { - r = manager_llmnr_ipv6_udp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - - r = manager_llmnr_ipv6_tcp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - } - - return 0; - -eaddrinuse: - log_warning("There appears to be another LLMNR responder running. Turning off LLMNR support."); - m->llmnr_support = RESOLVE_SUPPORT_NO; - manager_llmnr_stop(m); - - return 0; -} - -static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - DnsTransaction *t = NULL; - Manager *m = userdata; - DnsScope *scope; - int r; - - assert(s); - assert(fd >= 0); - assert(m); - - r = manager_recv(m, fd, DNS_PROTOCOL_LLMNR, &p); - if (r <= 0) - return r; - - scope = manager_find_scope(m, p); - if (!scope) - log_warning("Got LLMNR UDP packet on unknown scope. Ignoring."); - else if (dns_packet_validate_reply(p) > 0) { - log_debug("Got LLMNR UDP reply packet for id %u", DNS_PACKET_ID(p)); - - dns_scope_check_conflicts(scope, p); - - t = hashmap_get(m->dns_transactions, UINT_TO_PTR(DNS_PACKET_ID(p))); - if (t) - dns_transaction_process_reply(t, p); - - } else if (dns_packet_validate_query(p) > 0) { - log_debug("Got LLMNR UDP query packet for id %u", DNS_PACKET_ID(p)); - - dns_scope_process_query(scope, NULL, p); - } else - log_debug("Invalid LLMNR UDP packet, ignoring."); - - return 0; -} - -int manager_llmnr_ipv4_udp_fd(Manager *m) { - union sockaddr_union sa = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(LLMNR_PORT), - }; - static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255; - int r; - - assert(m); - - if (m->llmnr_ipv4_udp_fd >= 0) - return m->llmnr_ipv4_udp_fd; - - m->llmnr_ipv4_udp_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->llmnr_ipv4_udp_fd < 0) - return -errno; - - /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* Disable Don't-Fragment bit in the IP header */ - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->llmnr_ipv4_udp_fd, &sa.sa, sizeof(sa.in)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->llmnr_ipv4_udp_event_source, m->llmnr_ipv4_udp_fd, EPOLLIN, on_llmnr_packet, m); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(m->llmnr_ipv4_udp_event_source, "llmnr-ipv4-udp"); - - return m->llmnr_ipv4_udp_fd; - -fail: - m->llmnr_ipv4_udp_fd = safe_close(m->llmnr_ipv4_udp_fd); - return r; -} - -int manager_llmnr_ipv6_udp_fd(Manager *m) { - union sockaddr_union sa = { - .in6.sin6_family = AF_INET6, - .in6.sin6_port = htobe16(LLMNR_PORT), - }; - static const int one = 1, ttl = 255; - int r; - - assert(m); - - if (m->llmnr_ipv6_udp_fd >= 0) - return m->llmnr_ipv6_udp_fd; - - m->llmnr_ipv6_udp_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->llmnr_ipv6_udp_fd < 0) - return -errno; - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->llmnr_ipv6_udp_fd, &sa.sa, sizeof(sa.in6)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->llmnr_ipv6_udp_event_source, m->llmnr_ipv6_udp_fd, EPOLLIN, on_llmnr_packet, m); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(m->llmnr_ipv6_udp_event_source, "llmnr-ipv6-udp"); - - return m->llmnr_ipv6_udp_fd; - -fail: - m->llmnr_ipv6_udp_fd = safe_close(m->llmnr_ipv6_udp_fd); - return r; -} - -static int on_llmnr_stream_packet(DnsStream *s) { - DnsScope *scope; - - assert(s); - assert(s->read_packet); - - scope = manager_find_scope(s->manager, s->read_packet); - if (!scope) - log_warning("Got LLMNR TCP packet on unknown scope. Ignoring."); - else if (dns_packet_validate_query(s->read_packet) > 0) { - log_debug("Got LLMNR TCP query packet for id %u", DNS_PACKET_ID(s->read_packet)); - - dns_scope_process_query(scope, s, s->read_packet); - } else - log_debug("Invalid LLMNR TCP packet, ignoring."); - - dns_stream_unref(s); - return 0; -} - -static int on_llmnr_stream(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - DnsStream *stream; - Manager *m = userdata; - int cfd, r; - - cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); - if (cfd < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return -errno; - } - - r = dns_stream_new(m, &stream, DNS_PROTOCOL_LLMNR, cfd); - if (r < 0) { - safe_close(cfd); - return r; - } - - stream->on_packet = on_llmnr_stream_packet; - return 0; -} - -int manager_llmnr_ipv4_tcp_fd(Manager *m) { - union sockaddr_union sa = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(LLMNR_PORT), - }; - static const int one = 1, pmtu = IP_PMTUDISC_DONT; - int r; - - assert(m); - - if (m->llmnr_ipv4_tcp_fd >= 0) - return m->llmnr_ipv4_tcp_fd; - - m->llmnr_ipv4_tcp_fd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->llmnr_ipv4_tcp_fd < 0) - return -errno; - - /* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */ - r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_TTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* Disable Don't-Fragment bit in the IP header */ - r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->llmnr_ipv4_tcp_fd, &sa.sa, sizeof(sa.in)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = listen(m->llmnr_ipv4_tcp_fd, SOMAXCONN); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->llmnr_ipv4_tcp_event_source, m->llmnr_ipv4_tcp_fd, EPOLLIN, on_llmnr_stream, m); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(m->llmnr_ipv4_tcp_event_source, "llmnr-ipv4-tcp"); - - return m->llmnr_ipv4_tcp_fd; - -fail: - m->llmnr_ipv4_tcp_fd = safe_close(m->llmnr_ipv4_tcp_fd); - return r; -} - -int manager_llmnr_ipv6_tcp_fd(Manager *m) { - union sockaddr_union sa = { - .in6.sin6_family = AF_INET6, - .in6.sin6_port = htobe16(LLMNR_PORT), - }; - static const int one = 1; - int r; - - assert(m); - - if (m->llmnr_ipv6_tcp_fd >= 0) - return m->llmnr_ipv6_tcp_fd; - - m->llmnr_ipv6_tcp_fd = socket(AF_INET6, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->llmnr_ipv6_tcp_fd < 0) - return -errno; - - /* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */ - r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->llmnr_ipv6_tcp_fd, &sa.sa, sizeof(sa.in6)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = listen(m->llmnr_ipv6_tcp_fd, SOMAXCONN); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->llmnr_ipv6_tcp_event_source, m->llmnr_ipv6_tcp_fd, EPOLLIN, on_llmnr_stream, m); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(m->llmnr_ipv6_tcp_event_source, "llmnr-ipv6-tcp"); - - return m->llmnr_ipv6_tcp_fd; - -fail: - m->llmnr_ipv6_tcp_fd = safe_close(m->llmnr_ipv6_tcp_fd); - return r; -} diff --git a/src/resolve/resolved-llmnr.h b/src/resolve/resolved-llmnr.h deleted file mode 100644 index 8133582fa7..0000000000 --- a/src/resolve/resolved-llmnr.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include "resolved-manager.h" - -#define LLMNR_PORT 5355 - -int manager_llmnr_ipv4_udp_fd(Manager *m); -int manager_llmnr_ipv6_udp_fd(Manager *m); -int manager_llmnr_ipv4_tcp_fd(Manager *m); -int manager_llmnr_ipv6_tcp_fd(Manager *m); - -void manager_llmnr_stop(Manager *m); -int manager_llmnr_start(Manager *m); diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c deleted file mode 100644 index 6630585d13..0000000000 --- a/src/resolve/resolved-manager.c +++ /dev/null @@ -1,1377 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen <teg@jklm.no> - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. - ***/ - -#include <netinet/in.h> -#include <poll.h> -#include <sys/ioctl.h> - -#include "af-list.h" -#include "alloc-util.h" -#include "dirent-util.h" -#include "dns-domain.h" -#include "fd-util.h" -#include "fileio-label.h" -#include "hostname-util.h" -#include "io-util.h" -#include "netlink-util.h" -#include "network-internal.h" -#include "ordered-set.h" -#include "parse-util.h" -#include "random-util.h" -#include "resolved-bus.h" -#include "resolved-conf.h" -#include "resolved-dns-stub.h" -#include "resolved-etc-hosts.h" -#include "resolved-llmnr.h" -#include "resolved-manager.h" -#include "resolved-mdns.h" -#include "resolved-resolv-conf.h" -#include "socket-util.h" -#include "string-table.h" -#include "string-util.h" -#include "utf8.h" - -#define SEND_TIMEOUT_USEC (200 * USEC_PER_MSEC) - -static int manager_process_link(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) { - Manager *m = userdata; - uint16_t type; - Link *l; - int ifindex, r; - - assert(rtnl); - assert(m); - assert(mm); - - r = sd_netlink_message_get_type(mm, &type); - if (r < 0) - goto fail; - - r = sd_rtnl_message_link_get_ifindex(mm, &ifindex); - if (r < 0) - goto fail; - - l = hashmap_get(m->links, INT_TO_PTR(ifindex)); - - switch (type) { - - case RTM_NEWLINK:{ - bool is_new = !l; - - if (!l) { - r = link_new(m, &l, ifindex); - if (r < 0) - goto fail; - } - - r = link_process_rtnl(l, mm); - if (r < 0) - goto fail; - - r = link_update(l); - if (r < 0) - goto fail; - - if (is_new) - log_debug("Found new link %i/%s", ifindex, l->name); - - break; - } - - case RTM_DELLINK: - if (l) { - log_debug("Removing link %i/%s", l->ifindex, l->name); - link_remove_user(l); - link_free(l); - } - - break; - } - - return 0; - -fail: - log_warning_errno(r, "Failed to process RTNL link message: %m"); - return 0; -} - -static int manager_process_address(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) { - Manager *m = userdata; - union in_addr_union address; - uint16_t type; - int r, ifindex, family; - LinkAddress *a; - Link *l; - - assert(rtnl); - assert(mm); - assert(m); - - r = sd_netlink_message_get_type(mm, &type); - if (r < 0) - goto fail; - - r = sd_rtnl_message_addr_get_ifindex(mm, &ifindex); - if (r < 0) - goto fail; - - l = hashmap_get(m->links, INT_TO_PTR(ifindex)); - if (!l) - return 0; - - r = sd_rtnl_message_addr_get_family(mm, &family); - if (r < 0) - goto fail; - - switch (family) { - - case AF_INET: - r = sd_netlink_message_read_in_addr(mm, IFA_LOCAL, &address.in); - if (r < 0) { - r = sd_netlink_message_read_in_addr(mm, IFA_ADDRESS, &address.in); - if (r < 0) - goto fail; - } - - break; - - case AF_INET6: - r = sd_netlink_message_read_in6_addr(mm, IFA_LOCAL, &address.in6); - if (r < 0) { - r = sd_netlink_message_read_in6_addr(mm, IFA_ADDRESS, &address.in6); - if (r < 0) - goto fail; - } - - break; - - default: - return 0; - } - - a = link_find_address(l, family, &address); - - switch (type) { - - case RTM_NEWADDR: - - if (!a) { - r = link_address_new(l, &a, family, &address); - if (r < 0) - return r; - } - - r = link_address_update_rtnl(a, mm); - if (r < 0) - return r; - - break; - - case RTM_DELADDR: - link_address_free(a); - break; - } - - return 0; - -fail: - log_warning_errno(r, "Failed to process RTNL address message: %m"); - return 0; -} - -static int manager_rtnl_listen(Manager *m) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; - sd_netlink_message *i; - int r; - - assert(m); - - /* First, subscribe to interfaces coming and going */ - r = sd_netlink_open(&m->rtnl); - if (r < 0) - return r; - - r = sd_netlink_attach_event(m->rtnl, m->event, SD_EVENT_PRIORITY_IMPORTANT); - if (r < 0) - return r; - - r = sd_netlink_add_match(m->rtnl, RTM_NEWLINK, manager_process_link, m); - if (r < 0) - return r; - - r = sd_netlink_add_match(m->rtnl, RTM_DELLINK, manager_process_link, m); - if (r < 0) - return r; - - r = sd_netlink_add_match(m->rtnl, RTM_NEWADDR, manager_process_address, m); - if (r < 0) - return r; - - r = sd_netlink_add_match(m->rtnl, RTM_DELADDR, manager_process_address, m); - if (r < 0) - return r; - - /* Then, enumerate all links */ - r = sd_rtnl_message_new_link(m->rtnl, &req, RTM_GETLINK, 0); - if (r < 0) - return r; - - r = sd_netlink_message_request_dump(req, true); - if (r < 0) - return r; - - r = sd_netlink_call(m->rtnl, req, 0, &reply); - if (r < 0) - return r; - - for (i = reply; i; i = sd_netlink_message_next(i)) { - r = manager_process_link(m->rtnl, i, m); - if (r < 0) - return r; - } - - req = sd_netlink_message_unref(req); - reply = sd_netlink_message_unref(reply); - - /* Finally, enumerate all addresses, too */ - r = sd_rtnl_message_new_addr(m->rtnl, &req, RTM_GETADDR, 0, AF_UNSPEC); - if (r < 0) - return r; - - r = sd_netlink_message_request_dump(req, true); - if (r < 0) - return r; - - r = sd_netlink_call(m->rtnl, req, 0, &reply); - if (r < 0) - return r; - - for (i = reply; i; i = sd_netlink_message_next(i)) { - r = manager_process_address(m->rtnl, i, m); - if (r < 0) - return r; - } - - return r; -} - -static int on_network_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - Iterator i; - Link *l; - int r; - - assert(m); - - sd_network_monitor_flush(m->network_monitor); - - HASHMAP_FOREACH(l, m->links, i) { - r = link_update(l); - if (r < 0) - log_warning_errno(r, "Failed to update monitor information for %i: %m", l->ifindex); - } - - (void) manager_write_resolv_conf(m); - - return 0; -} - -static int manager_network_monitor_listen(Manager *m) { - int r, fd, events; - - assert(m); - - r = sd_network_monitor_new(&m->network_monitor, NULL); - if (r < 0) - return r; - - fd = sd_network_monitor_get_fd(m->network_monitor); - if (fd < 0) - return fd; - - events = sd_network_monitor_get_events(m->network_monitor); - if (events < 0) - return events; - - r = sd_event_add_io(m->event, &m->network_event_source, fd, events, &on_network_event, m); - if (r < 0) - return r; - - r = sd_event_source_set_priority(m->network_event_source, SD_EVENT_PRIORITY_IMPORTANT+5); - if (r < 0) - return r; - - (void) sd_event_source_set_description(m->network_event_source, "network-monitor"); - - return 0; -} - -static int determine_hostname(char **llmnr_hostname, char **mdns_hostname) { - _cleanup_free_ char *h = NULL, *n = NULL; - char label[DNS_LABEL_MAX]; - const char *p; - int r, k; - - assert(llmnr_hostname); - assert(mdns_hostname); - - /* Extract and normalize the first label of the locally - * configured hostname, and check it's not "localhost". */ - - h = gethostname_malloc(); - if (!h) - return log_oom(); - - p = h; - r = dns_label_unescape(&p, label, sizeof(label)); - if (r < 0) - return log_error_errno(r, "Failed to unescape host name: %m"); - if (r == 0) { - log_error("Couldn't find a single label in hosntame."); - return -EINVAL; - } - - k = dns_label_undo_idna(label, r, label, sizeof(label)); - if (k < 0) - return log_error_errno(k, "Failed to undo IDNA: %m"); - if (k > 0) - r = k; - - if (!utf8_is_valid(label)) { - log_error("System hostname is not UTF-8 clean."); - return -EINVAL; - } - - r = dns_label_escape_new(label, r, &n); - if (r < 0) - return log_error_errno(r, "Failed to escape host name: %m"); - - if (is_localhost(n)) { - log_debug("System hostname is 'localhost', ignoring."); - return -EINVAL; - } - - r = dns_name_concat(n, "local", mdns_hostname); - if (r < 0) - return log_error_errno(r, "Failed to determine mDNS hostname: %m"); - - *llmnr_hostname = n; - n = NULL; - - return 0; -} - -static int on_hostname_change(sd_event_source *es, int fd, uint32_t revents, void *userdata) { - _cleanup_free_ char *llmnr_hostname = NULL, *mdns_hostname = NULL; - Manager *m = userdata; - int r; - - assert(m); - - r = determine_hostname(&llmnr_hostname, &mdns_hostname); - if (r < 0) - return 0; /* ignore invalid hostnames */ - - if (streq(llmnr_hostname, m->llmnr_hostname) && streq(mdns_hostname, m->mdns_hostname)) - return 0; - - log_info("System hostname changed to '%s'.", llmnr_hostname); - - free(m->llmnr_hostname); - free(m->mdns_hostname); - - m->llmnr_hostname = llmnr_hostname; - m->mdns_hostname = mdns_hostname; - - llmnr_hostname = mdns_hostname = NULL; - - manager_refresh_rrs(m); - - return 0; -} - -static int manager_watch_hostname(Manager *m) { - int r; - - assert(m); - - m->hostname_fd = open("/proc/sys/kernel/hostname", O_RDONLY|O_CLOEXEC|O_NDELAY|O_NOCTTY); - if (m->hostname_fd < 0) { - log_warning_errno(errno, "Failed to watch hostname: %m"); - return 0; - } - - r = sd_event_add_io(m->event, &m->hostname_event_source, m->hostname_fd, 0, on_hostname_change, m); - if (r < 0) { - if (r == -EPERM) - /* kernels prior to 3.2 don't support polling this file. Ignore the failure. */ - m->hostname_fd = safe_close(m->hostname_fd); - else - return log_error_errno(r, "Failed to add hostname event source: %m"); - } - - (void) sd_event_source_set_description(m->hostname_event_source, "hostname"); - - r = determine_hostname(&m->llmnr_hostname, &m->mdns_hostname); - if (r < 0) { - log_info("Defaulting to hostname 'gnu-linux'."); - m->llmnr_hostname = strdup("gnu-linux"); - if (!m->llmnr_hostname) - return log_oom(); - - m->mdns_hostname = strdup("gnu-linux.local"); - if (!m->mdns_hostname) - return log_oom(); - } else - log_info("Using system hostname '%s'.", m->llmnr_hostname); - - return 0; -} - -static int manager_sigusr1(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - _cleanup_free_ char *buffer = NULL; - _cleanup_fclose_ FILE *f = NULL; - Manager *m = userdata; - size_t size = 0; - DnsScope *scope; - - assert(s); - assert(si); - assert(m); - - f = open_memstream(&buffer, &size); - if (!f) - return log_oom(); - - LIST_FOREACH(scopes, scope, m->dns_scopes) - dns_scope_dump(scope, f); - - if (fflush_and_check(f) < 0) - return log_oom(); - - log_dump(LOG_INFO, buffer); - return 0; -} - -static int manager_sigusr2(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - Manager *m = userdata; - - assert(s); - assert(si); - assert(m); - - manager_flush_caches(m); - - return 0; -} - -int manager_new(Manager **ret) { - _cleanup_(manager_freep) Manager *m = NULL; - int r; - - assert(ret); - - m = new0(Manager, 1); - if (!m) - return -ENOMEM; - - m->llmnr_ipv4_udp_fd = m->llmnr_ipv6_udp_fd = -1; - m->llmnr_ipv4_tcp_fd = m->llmnr_ipv6_tcp_fd = -1; - m->mdns_ipv4_fd = m->mdns_ipv6_fd = -1; - m->dns_stub_udp_fd = m->dns_stub_tcp_fd = -1; - m->hostname_fd = -1; - - m->llmnr_support = RESOLVE_SUPPORT_YES; - m->mdns_support = RESOLVE_SUPPORT_NO; - m->dnssec_mode = DEFAULT_DNSSEC_MODE; - m->enable_cache = true; - m->dns_stub_listener_mode = DNS_STUB_LISTENER_UDP; - 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) - return r; - - r = manager_parse_config_file(m); - if (r < 0) - return r; - - r = sd_event_default(&m->event); - if (r < 0) - return r; - - sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL); - sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL); - - sd_event_set_watchdog(m->event, true); - - r = manager_watch_hostname(m); - if (r < 0) - return r; - - r = dns_scope_new(m, &m->unicast_scope, NULL, DNS_PROTOCOL_DNS, AF_UNSPEC); - if (r < 0) - return r; - - r = manager_network_monitor_listen(m); - if (r < 0) - return r; - - r = manager_rtnl_listen(m); - if (r < 0) - return r; - - r = manager_connect_bus(m); - if (r < 0) - return r; - - (void) sd_event_add_signal(m->event, &m->sigusr1_event_source, SIGUSR1, manager_sigusr1, m); - (void) sd_event_add_signal(m->event, &m->sigusr2_event_source, SIGUSR2, manager_sigusr2, m); - - manager_cleanup_saved_user(m); - - *ret = m; - m = NULL; - - return 0; -} - -int manager_start(Manager *m) { - int r; - - assert(m); - - r = manager_dns_stub_start(m); - if (r < 0) - return r; - - r = manager_llmnr_start(m); - if (r < 0) - return r; - - r = manager_mdns_start(m); - if (r < 0) - return r; - - return 0; -} - -Manager *manager_free(Manager *m) { - Link *l; - - if (!m) - return NULL; - - dns_server_unlink_all(m->dns_servers); - dns_server_unlink_all(m->fallback_dns_servers); - dns_search_domain_unlink_all(m->search_domains); - - while ((l = hashmap_first(m->links))) - link_free(l); - - while (m->dns_queries) - dns_query_free(m->dns_queries); - - dns_scope_free(m->unicast_scope); - - /* At this point only orphaned streams should remain. All others should have been freed already by their - * owners */ - while (m->dns_streams) - dns_stream_unref(m->dns_streams); - - hashmap_free(m->links); - hashmap_free(m->dns_transactions); - - sd_event_source_unref(m->network_event_source); - sd_network_monitor_unref(m->network_monitor); - - sd_netlink_unref(m->rtnl); - sd_event_source_unref(m->rtnl_event_source); - - manager_llmnr_stop(m); - manager_mdns_stop(m); - manager_dns_stub_stop(m); - - sd_bus_slot_unref(m->prepare_for_sleep_slot); - sd_event_source_unref(m->bus_retry_event_source); - sd_bus_unref(m->bus); - - sd_event_source_unref(m->sigusr1_event_source); - sd_event_source_unref(m->sigusr2_event_source); - - sd_event_unref(m->event); - - dns_resource_key_unref(m->llmnr_host_ipv4_key); - dns_resource_key_unref(m->llmnr_host_ipv6_key); - - sd_event_source_unref(m->hostname_event_source); - safe_close(m->hostname_fd); - free(m->llmnr_hostname); - free(m->mdns_hostname); - - dns_trust_anchor_flush(&m->trust_anchor); - manager_etc_hosts_flush(m); - - return mfree(m); -} - -int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - union { - struct cmsghdr header; /* For alignment */ - uint8_t buffer[CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo)) - + CMSG_SPACE(int) /* ttl/hoplimit */ - + EXTRA_CMSG_SPACE /* kernel appears to require extra buffer space */]; - } control; - union sockaddr_union sa; - struct msghdr mh = {}; - struct cmsghdr *cmsg; - struct iovec iov; - ssize_t ms, l; - int r; - - assert(m); - assert(fd >= 0); - assert(ret); - - ms = next_datagram_size_fd(fd); - if (ms < 0) - return ms; - - r = dns_packet_new(&p, protocol, ms); - if (r < 0) - return r; - - iov.iov_base = DNS_PACKET_DATA(p); - iov.iov_len = p->allocated; - - mh.msg_name = &sa.sa; - mh.msg_namelen = sizeof(sa); - mh.msg_iov = &iov; - mh.msg_iovlen = 1; - mh.msg_control = &control; - mh.msg_controllen = sizeof(control); - - l = recvmsg(fd, &mh, 0); - if (l == 0) - return 0; - if (l < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return -errno; - } - - assert(!(mh.msg_flags & MSG_CTRUNC)); - assert(!(mh.msg_flags & MSG_TRUNC)); - - p->size = (size_t) l; - - p->family = sa.sa.sa_family; - p->ipproto = IPPROTO_UDP; - if (p->family == AF_INET) { - p->sender.in = sa.in.sin_addr; - p->sender_port = be16toh(sa.in.sin_port); - } else if (p->family == AF_INET6) { - p->sender.in6 = sa.in6.sin6_addr; - p->sender_port = be16toh(sa.in6.sin6_port); - p->ifindex = sa.in6.sin6_scope_id; - } else - return -EAFNOSUPPORT; - - CMSG_FOREACH(cmsg, &mh) { - - if (cmsg->cmsg_level == IPPROTO_IPV6) { - assert(p->family == AF_INET6); - - switch (cmsg->cmsg_type) { - - case IPV6_PKTINFO: { - struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg); - - if (p->ifindex <= 0) - p->ifindex = i->ipi6_ifindex; - - p->destination.in6 = i->ipi6_addr; - break; - } - - case IPV6_HOPLIMIT: - p->ttl = *(int *) CMSG_DATA(cmsg); - break; - - } - } else if (cmsg->cmsg_level == IPPROTO_IP) { - assert(p->family == AF_INET); - - switch (cmsg->cmsg_type) { - - case IP_PKTINFO: { - struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg); - - if (p->ifindex <= 0) - p->ifindex = i->ipi_ifindex; - - p->destination.in = i->ipi_addr; - break; - } - - case IP_TTL: - p->ttl = *(int *) CMSG_DATA(cmsg); - break; - } - } - } - - /* The Linux kernel sets the interface index to the loopback - * device if the packet came from the local host since it - * avoids the routing table in such a case. Let's unset the - * interface index in such a case. */ - if (p->ifindex == LOOPBACK_IFINDEX) - p->ifindex = 0; - - if (protocol != DNS_PROTOCOL_DNS) { - /* If we don't know the interface index still, we look for the - * first local interface with a matching address. Yuck! */ - if (p->ifindex <= 0) - p->ifindex = manager_find_ifindex(m, p->family, &p->destination); - } - - *ret = p; - p = NULL; - - return 1; -} - -static int sendmsg_loop(int fd, struct msghdr *mh, int flags) { - int r; - - assert(fd >= 0); - assert(mh); - - for (;;) { - if (sendmsg(fd, mh, flags) >= 0) - return 0; - - if (errno == EINTR) - continue; - - if (errno != EAGAIN) - return -errno; - - r = fd_wait_for_event(fd, POLLOUT, SEND_TIMEOUT_USEC); - if (r < 0) - return r; - if (r == 0) - return -ETIMEDOUT; - } -} - -static int write_loop(int fd, void *message, size_t length) { - int r; - - assert(fd >= 0); - assert(message); - - for (;;) { - if (write(fd, message, length) >= 0) - return 0; - - if (errno == EINTR) - continue; - - if (errno != EAGAIN) - return -errno; - - r = fd_wait_for_event(fd, POLLOUT, SEND_TIMEOUT_USEC); - if (r < 0) - return r; - if (r == 0) - return -ETIMEDOUT; - } -} - -int manager_write(Manager *m, int fd, DnsPacket *p) { - int r; - - log_debug("Sending %s packet with id %" PRIu16 ".", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p)); - - r = write_loop(fd, DNS_PACKET_DATA(p), p->size); - if (r < 0) - return r; - - return 0; -} - -static int manager_ipv4_send( - Manager *m, - int fd, - int ifindex, - const struct in_addr *destination, - uint16_t port, - const struct in_addr *source, - DnsPacket *p) { - union sockaddr_union sa = { - .in.sin_family = AF_INET, - }; - union { - struct cmsghdr header; /* For alignment */ - uint8_t buffer[CMSG_SPACE(sizeof(struct in_pktinfo))]; - } control; - struct msghdr mh = {}; - struct iovec iov; - - assert(m); - assert(fd >= 0); - assert(destination); - assert(port > 0); - assert(p); - - iov.iov_base = DNS_PACKET_DATA(p); - iov.iov_len = p->size; - - sa.in.sin_addr = *destination; - sa.in.sin_port = htobe16(port), - - mh.msg_iov = &iov; - mh.msg_iovlen = 1; - mh.msg_name = &sa.sa; - mh.msg_namelen = sizeof(sa.in); - - if (ifindex > 0) { - struct cmsghdr *cmsg; - struct in_pktinfo *pi; - - zero(control); - - mh.msg_control = &control; - mh.msg_controllen = CMSG_LEN(sizeof(struct in_pktinfo)); - - cmsg = CMSG_FIRSTHDR(&mh); - cmsg->cmsg_len = mh.msg_controllen; - cmsg->cmsg_level = IPPROTO_IP; - cmsg->cmsg_type = IP_PKTINFO; - - pi = (struct in_pktinfo*) CMSG_DATA(cmsg); - pi->ipi_ifindex = ifindex; - - if (source) - pi->ipi_spec_dst = *source; - } - - return sendmsg_loop(fd, &mh, 0); -} - -static int manager_ipv6_send( - Manager *m, - int fd, - int ifindex, - const struct in6_addr *destination, - uint16_t port, - const struct in6_addr *source, - DnsPacket *p) { - - union sockaddr_union sa = { - .in6.sin6_family = AF_INET6, - }; - union { - struct cmsghdr header; /* For alignment */ - uint8_t buffer[CMSG_SPACE(sizeof(struct in6_pktinfo))]; - } control; - struct msghdr mh = {}; - struct iovec iov; - - assert(m); - assert(fd >= 0); - assert(destination); - assert(port > 0); - assert(p); - - iov.iov_base = DNS_PACKET_DATA(p); - iov.iov_len = p->size; - - sa.in6.sin6_addr = *destination; - sa.in6.sin6_port = htobe16(port), - sa.in6.sin6_scope_id = ifindex; - - mh.msg_iov = &iov; - mh.msg_iovlen = 1; - mh.msg_name = &sa.sa; - mh.msg_namelen = sizeof(sa.in6); - - if (ifindex > 0) { - struct cmsghdr *cmsg; - struct in6_pktinfo *pi; - - zero(control); - - mh.msg_control = &control; - mh.msg_controllen = CMSG_LEN(sizeof(struct in6_pktinfo)); - - cmsg = CMSG_FIRSTHDR(&mh); - cmsg->cmsg_len = mh.msg_controllen; - cmsg->cmsg_level = IPPROTO_IPV6; - cmsg->cmsg_type = IPV6_PKTINFO; - - pi = (struct in6_pktinfo*) CMSG_DATA(cmsg); - pi->ipi6_ifindex = ifindex; - - if (source) - pi->ipi6_addr = *source; - } - - return sendmsg_loop(fd, &mh, 0); -} - -int manager_send( - Manager *m, - int fd, - int ifindex, - int family, - const union in_addr_union *destination, - uint16_t port, - const union in_addr_union *source, - DnsPacket *p) { - - assert(m); - assert(fd >= 0); - assert(destination); - assert(port > 0); - assert(p); - - log_debug("Sending %s packet with id %" PRIu16 " on interface %i/%s.", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p), ifindex, af_to_name(family)); - - if (family == AF_INET) - return manager_ipv4_send(m, fd, ifindex, &destination->in, port, &source->in, p); - if (family == AF_INET6) - return manager_ipv6_send(m, fd, ifindex, &destination->in6, port, &source->in6, p); - - return -EAFNOSUPPORT; -} - -uint32_t manager_find_mtu(Manager *m) { - uint32_t mtu = 0; - Link *l; - Iterator i; - - /* If we don't know on which link a DNS packet would be - * delivered, let's find the largest MTU that works on all - * interfaces we know of */ - - HASHMAP_FOREACH(l, m->links, i) { - if (l->mtu <= 0) - continue; - - if (mtu <= 0 || l->mtu < mtu) - mtu = l->mtu; - } - - return mtu; -} - -int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_addr) { - LinkAddress *a; - - assert(m); - - a = manager_find_link_address(m, family, in_addr); - if (a) - return a->link->ifindex; - - return 0; -} - -void manager_refresh_rrs(Manager *m) { - Iterator i; - Link *l; - - assert(m); - - m->llmnr_host_ipv4_key = dns_resource_key_unref(m->llmnr_host_ipv4_key); - m->llmnr_host_ipv6_key = dns_resource_key_unref(m->llmnr_host_ipv6_key); - - HASHMAP_FOREACH(l, m->links, i) { - link_add_rrs(l, true); - link_add_rrs(l, false); - } -} - -int manager_next_hostname(Manager *m) { - const char *p; - uint64_t u, a; - char *h, *k; - int r; - - assert(m); - - p = strchr(m->llmnr_hostname, 0); - assert(p); - - while (p > m->llmnr_hostname) { - if (!strchr("0123456789", p[-1])) - break; - - p--; - } - - if (*p == 0 || safe_atou64(p, &u) < 0 || u <= 0) - u = 1; - - /* Add a random number to the old value. This way we can avoid - * that two hosts pick the same hostname, win on IPv4 and lose - * on IPv6 (or vice versa), and pick the same hostname - * replacement hostname, ad infinitum. We still want the - * numbers to go up monotonically, hence we just add a random - * value 1..10 */ - - random_bytes(&a, sizeof(a)); - u += 1 + a % 10; - - if (asprintf(&h, "%.*s%" PRIu64, (int) (p - m->llmnr_hostname), m->llmnr_hostname, u) < 0) - return -ENOMEM; - - r = dns_name_concat(h, "local", &k); - if (r < 0) { - free(h); - return r; - } - - log_info("Hostname conflict, changing published hostname from '%s' to '%s'.", m->llmnr_hostname, h); - - free(m->llmnr_hostname); - m->llmnr_hostname = h; - - free(m->mdns_hostname); - m->mdns_hostname = k; - - manager_refresh_rrs(m); - - return 0; -} - -LinkAddress* manager_find_link_address(Manager *m, int family, const union in_addr_union *in_addr) { - Iterator i; - Link *l; - - assert(m); - - HASHMAP_FOREACH(l, m->links, i) { - LinkAddress *a; - - a = link_find_address(l, family, in_addr); - if (a) - return a; - } - - return NULL; -} - -bool manager_our_packet(Manager *m, DnsPacket *p) { - assert(m); - assert(p); - - return !!manager_find_link_address(m, p->family, &p->sender); -} - -DnsScope* manager_find_scope(Manager *m, DnsPacket *p) { - Link *l; - - assert(m); - assert(p); - - l = hashmap_get(m->links, INT_TO_PTR(p->ifindex)); - if (!l) - return NULL; - - switch (p->protocol) { - case DNS_PROTOCOL_LLMNR: - if (p->family == AF_INET) - return l->llmnr_ipv4_scope; - else if (p->family == AF_INET6) - return l->llmnr_ipv6_scope; - - break; - - case DNS_PROTOCOL_MDNS: - if (p->family == AF_INET) - return l->mdns_ipv4_scope; - else if (p->family == AF_INET6) - return l->mdns_ipv6_scope; - - break; - - default: - break; - } - - return NULL; -} - -void manager_verify_all(Manager *m) { - DnsScope *s; - - assert(m); - - LIST_FOREACH(scopes, s, m->dns_scopes) - dns_zone_verify_all(&s->zone); -} - -int manager_is_own_hostname(Manager *m, const char *name) { - int r; - - assert(m); - assert(name); - - if (m->llmnr_hostname) { - r = dns_name_equal(name, m->llmnr_hostname); - if (r != 0) - return r; - } - - if (m->mdns_hostname) - return dns_name_equal(name, m->mdns_hostname); - - return 0; -} - -int manager_compile_dns_servers(Manager *m, OrderedSet **dns) { - DnsServer *s; - Iterator i; - Link *l; - int r; - - assert(m); - assert(dns); - - r = ordered_set_ensure_allocated(dns, &dns_server_hash_ops); - if (r < 0) - return r; - - /* First add the system-wide servers and domains */ - LIST_FOREACH(servers, s, m->dns_servers) { - r = ordered_set_put(*dns, s); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - - /* Then, add the per-link servers */ - HASHMAP_FOREACH(l, m->links, i) { - LIST_FOREACH(servers, s, l->dns_servers) { - r = ordered_set_put(*dns, s); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - } - - /* If we found nothing, add the fallback servers */ - if (ordered_set_isempty(*dns)) { - LIST_FOREACH(servers, s, m->fallback_dns_servers) { - r = ordered_set_put(*dns, s); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - } - - return 0; -} - -/* filter_route is a tri-state: - * < 0: no filtering - * = 0 or false: return only domains which should be used for searching - * > 0 or true: return only domains which are for routing only - */ -int manager_compile_search_domains(Manager *m, OrderedSet **domains, int filter_route) { - DnsSearchDomain *d; - Iterator i; - Link *l; - int r; - - assert(m); - assert(domains); - - r = ordered_set_ensure_allocated(domains, &dns_name_hash_ops); - if (r < 0) - return r; - - LIST_FOREACH(domains, d, m->search_domains) { - - if (filter_route >= 0 && - d->route_only != !!filter_route) - continue; - - r = ordered_set_put(*domains, d->name); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - - HASHMAP_FOREACH(l, m->links, i) { - - LIST_FOREACH(domains, d, l->search_domains) { - - if (filter_route >= 0 && - d->route_only != !!filter_route) - continue; - - r = ordered_set_put(*domains, d->name); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - } - - return 0; -} - -DnssecMode manager_get_dnssec_mode(Manager *m) { - assert(m); - - if (m->dnssec_mode != _DNSSEC_MODE_INVALID) - return m->dnssec_mode; - - return DNSSEC_NO; -} - -bool manager_dnssec_supported(Manager *m) { - DnsServer *server; - Iterator i; - Link *l; - - assert(m); - - if (manager_get_dnssec_mode(m) == DNSSEC_NO) - return false; - - server = manager_get_dns_server(m); - if (server && !dns_server_dnssec_supported(server)) - return false; - - HASHMAP_FOREACH(l, m->links, i) - if (!link_dnssec_supported(l)) - return false; - - 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) { - char s[DNS_RESOURCE_KEY_STRING_MAX]; - - log_debug("Found verdict for lookup %s: %s", - dns_resource_key_to_string(key, s, sizeof s), - dnssec_verdict_to_string(verdict)); - } - - m->n_dnssec_verdict[verdict]++; -} - -bool manager_routable(Manager *m, int family) { - Iterator i; - Link *l; - - assert(m); - - /* Returns true if the host has at least one interface with a routable address of the specified type */ - - HASHMAP_FOREACH(l, m->links, i) - if (link_relevant(l, family, false)) - return true; - - return false; -} - -void manager_flush_caches(Manager *m) { - DnsScope *scope; - - assert(m); - - LIST_FOREACH(scopes, scope, m->dns_scopes) - dns_cache_flush(&scope->cache); - - log_info("Flushed all caches."); -} - -void manager_cleanup_saved_user(Manager *m) { - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - int r; - - assert(m); - - /* Clean up all saved per-link files in /run/systemd/resolve/netif/ that don't have a matching interface - * anymore. These files are created to persist settings pushed in by the user via the bus, so that resolved can - * be restarted without losing this data. */ - - d = opendir("/run/systemd/resolve/netif/"); - if (!d) { - if (errno == ENOENT) - return; - - log_warning_errno(errno, "Failed to open interface directory: %m"); - return; - } - - FOREACH_DIRENT_ALL(de, d, log_error_errno(errno, "Failed to read interface directory: %m")) { - _cleanup_free_ char *p = NULL; - int ifindex; - Link *l; - - if (!IN_SET(de->d_type, DT_UNKNOWN, DT_REG)) - continue; - - if (STR_IN_SET(de->d_name, ".", "..")) - continue; - - r = parse_ifindex(de->d_name, &ifindex); - if (r < 0) /* Probably some temporary file from a previous run. Delete it */ - goto rm; - - l = hashmap_get(m->links, INT_TO_PTR(ifindex)); - if (!l) /* link vanished */ - goto rm; - - if (l->is_managed) /* now managed by networkd, hence the bus settings are useless */ - goto rm; - - continue; - - rm: - p = strappend("/run/systemd/resolve/netif/", de->d_name); - if (!p) { - log_oom(); - return; - } - - (void) unlink(p); - } -} diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h deleted file mode 100644 index 6b2208ed94..0000000000 --- a/src/resolve/resolved-manager.h +++ /dev/null @@ -1,185 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen <teg@jklm.no> - - 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 "sd-event.h" -#include "sd-netlink.h" -#include "sd-network.h" - -#include "hashmap.h" -#include "list.h" -#include "ordered-set.h" -#include "resolve-util.h" - -typedef struct Manager Manager; - -#include "resolved-conf.h" -#include "resolved-dns-query.h" -#include "resolved-dns-search-domain.h" -#include "resolved-dns-server.h" -#include "resolved-dns-stream.h" -#include "resolved-dns-trust-anchor.h" -#include "resolved-link.h" - -#define MANAGER_SEARCH_DOMAINS_MAX 32 -#define MANAGER_DNS_SERVERS_MAX 32 - -struct Manager { - sd_event *event; - - ResolveSupport llmnr_support; - ResolveSupport mdns_support; - DnssecMode dnssec_mode; - bool enable_cache; - DnsStubListenerMode dns_stub_listener_mode; - - /* Network */ - Hashmap *links; - - sd_netlink *rtnl; - sd_event_source *rtnl_event_source; - - sd_network_monitor *network_monitor; - sd_event_source *network_event_source; - - /* DNS query management */ - Hashmap *dns_transactions; - LIST_HEAD(DnsQuery, dns_queries); - unsigned n_dns_queries; - - LIST_HEAD(DnsStream, dns_streams); - unsigned n_dns_streams; - - /* Unicast dns */ - LIST_HEAD(DnsServer, dns_servers); - LIST_HEAD(DnsServer, fallback_dns_servers); - unsigned n_dns_servers; /* counts both main and fallback */ - DnsServer *current_dns_server; - - LIST_HEAD(DnsSearchDomain, search_domains); - unsigned n_search_domains; - - bool need_builtin_fallbacks:1; - - bool read_resolv_conf:1; - usec_t resolv_conf_mtime; - - DnsTrustAnchor trust_anchor; - - LIST_HEAD(DnsScope, dns_scopes); - DnsScope *unicast_scope; - - /* LLMNR */ - int llmnr_ipv4_udp_fd; - int llmnr_ipv6_udp_fd; - int llmnr_ipv4_tcp_fd; - int llmnr_ipv6_tcp_fd; - - sd_event_source *llmnr_ipv4_udp_event_source; - sd_event_source *llmnr_ipv6_udp_event_source; - sd_event_source *llmnr_ipv4_tcp_event_source; - sd_event_source *llmnr_ipv6_tcp_event_source; - - /* mDNS */ - int mdns_ipv4_fd; - int mdns_ipv6_fd; - - sd_event_source *mdns_ipv4_event_source; - sd_event_source *mdns_ipv6_event_source; - - /* dbus */ - sd_bus *bus; - sd_event_source *bus_retry_event_source; - - /* The hostname we publish on LLMNR and mDNS */ - char *llmnr_hostname; - char *mdns_hostname; - DnsResourceKey *llmnr_host_ipv4_key; - DnsResourceKey *llmnr_host_ipv6_key; - - /* Watch the system hostname */ - int hostname_fd; - sd_event_source *hostname_event_source; - - /* Watch for system suspends */ - sd_bus_slot *prepare_for_sleep_slot; - - sd_event_source *sigusr1_event_source; - sd_event_source *sigusr2_event_source; - - unsigned n_transactions_total; - 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; - - /* Local DNS stub on 127.0.0.53:53 */ - int dns_stub_udp_fd; - int dns_stub_tcp_fd; - - sd_event_source *dns_stub_udp_event_source; - sd_event_source *dns_stub_tcp_event_source; -}; - -/* Manager */ - -int manager_new(Manager **ret); -Manager* manager_free(Manager *m); - -int manager_start(Manager *m); - -uint32_t manager_find_mtu(Manager *m); - -int manager_write(Manager *m, int fd, DnsPacket *p); -int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *destination, uint16_t port, const union in_addr_union *source, DnsPacket *p); -int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret); - -int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_addr); -LinkAddress* manager_find_link_address(Manager *m, int family, const union in_addr_union *in_addr); - -void manager_refresh_rrs(Manager *m); -int manager_next_hostname(Manager *m); - -bool manager_our_packet(Manager *m, DnsPacket *p); -DnsScope* manager_find_scope(Manager *m, DnsPacket *p); - -void manager_verify_all(Manager *m); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); - -#define EXTRA_CMSG_SPACE 1024 - -int manager_is_own_hostname(Manager *m, const char *name); - -int manager_compile_dns_servers(Manager *m, OrderedSet **servers); -int manager_compile_search_domains(Manager *m, OrderedSet **domains, int filter_route); - -DnssecMode manager_get_dnssec_mode(Manager *m); -bool manager_dnssec_supported(Manager *m); - -void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key); - -bool manager_routable(Manager *m, int family); - -void manager_flush_caches(Manager *m); - -void manager_cleanup_saved_user(Manager *m); diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c deleted file mode 100644 index b13b1d0144..0000000000 --- a/src/resolve/resolved-mdns.c +++ /dev/null @@ -1,287 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 Daniel Mack - - 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 <resolv.h> -#include <netinet/in.h> -#include <arpa/inet.h> - -#include "fd-util.h" -#include "resolved-manager.h" -#include "resolved-mdns.h" - -void manager_mdns_stop(Manager *m) { - assert(m); - - m->mdns_ipv4_event_source = sd_event_source_unref(m->mdns_ipv4_event_source); - m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd); - - m->mdns_ipv6_event_source = sd_event_source_unref(m->mdns_ipv6_event_source); - m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); -} - -int manager_mdns_start(Manager *m) { - int r; - - assert(m); - - if (m->mdns_support == RESOLVE_SUPPORT_NO) - return 0; - - r = manager_mdns_ipv4_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - - if (socket_ipv6_is_supported()) { - r = manager_mdns_ipv6_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - } - - return 0; - -eaddrinuse: - log_warning("There appears to be another mDNS responder running. Turning off mDNS support."); - m->mdns_support = RESOLVE_SUPPORT_NO; - manager_mdns_stop(m); - - return 0; -} - -static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - Manager *m = userdata; - DnsScope *scope; - int r; - - r = manager_recv(m, fd, DNS_PROTOCOL_MDNS, &p); - if (r <= 0) - return r; - - scope = manager_find_scope(m, p); - if (!scope) { - log_warning("Got mDNS UDP packet on unknown scope. Ignoring."); - return 0; - } - - if (dns_packet_validate_reply(p) > 0) { - DnsResourceRecord *rr; - - log_debug("Got mDNS reply packet"); - - /* - * mDNS is different from regular DNS and LLMNR with regard to handling responses. - * While on other protocols, we can ignore every answer that doesn't match a question - * we broadcast earlier, RFC6762, section 18.1 recommends looking at and caching all - * incoming information, regardless of the DNS packet ID. - * - * Hence, extract the packet here, and try to find a transaction for answer the we got - * and complete it. Also store the new information in scope's cache. - */ - r = dns_packet_extract(p); - if (r < 0) { - log_debug("mDNS packet extraction failed."); - return 0; - } - - dns_scope_check_conflicts(scope, p); - - DNS_ANSWER_FOREACH(rr, p->answer) { - const char *name = dns_resource_key_name(rr->key); - DnsTransaction *t; - - /* If the received reply packet contains ANY record that is not .local or .in-addr.arpa, - * we assume someone's playing tricks on us and discard the packet completely. */ - if (!(dns_name_endswith(name, "in-addr.arpa") > 0 || - dns_name_endswith(name, "local") > 0)) - return 0; - - t = dns_scope_find_transaction(scope, rr->key, false); - if (t) - dns_transaction_process_reply(t, p); - } - - dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, false, (uint32_t) -1, 0, p->family, &p->sender); - - } else if (dns_packet_validate_query(p) > 0) { - log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p)); - - dns_scope_process_query(scope, NULL, p); - } else - log_debug("Invalid mDNS UDP packet."); - - return 0; -} - -int manager_mdns_ipv4_fd(Manager *m) { - union sockaddr_union sa = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(MDNS_PORT), - }; - static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255; - int r; - - assert(m); - - if (m->mdns_ipv4_fd >= 0) - return m->mdns_ipv4_fd; - - m->mdns_ipv4_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->mdns_ipv4_fd < 0) - return -errno; - - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv4_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* Disable Don't-Fragment bit in the IP header */ - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->mdns_ipv4_fd, &sa.sa, sizeof(sa.in)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->mdns_ipv4_event_source, m->mdns_ipv4_fd, EPOLLIN, on_mdns_packet, m); - if (r < 0) - goto fail; - - return m->mdns_ipv4_fd; - -fail: - m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd); - return r; -} - -int manager_mdns_ipv6_fd(Manager *m) { - union sockaddr_union sa = { - .in6.sin6_family = AF_INET6, - .in6.sin6_port = htobe16(MDNS_PORT), - }; - static const int one = 1, ttl = 255; - int r; - - assert(m); - - if (m->mdns_ipv6_fd >= 0) - return m->mdns_ipv6_fd; - - m->mdns_ipv6_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->mdns_ipv6_fd < 0) - return -errno; - - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv6_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->mdns_ipv6_fd, &sa.sa, sizeof(sa.in6)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->mdns_ipv6_event_source, m->mdns_ipv6_fd, EPOLLIN, on_mdns_packet, m); - if (r < 0) - goto fail; - - return m->mdns_ipv6_fd; - -fail: - m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); - return r; -} diff --git a/src/resolve/resolved-mdns.h b/src/resolve/resolved-mdns.h deleted file mode 100644 index 5d274648f4..0000000000 --- a/src/resolve/resolved-mdns.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 Daniel Mack - - 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" - -#define MDNS_PORT 5353 - -int manager_mdns_ipv4_fd(Manager *m); -int manager_mdns_ipv6_fd(Manager *m); - -void manager_mdns_stop(Manager *m); -int manager_mdns_start(Manager *m); diff --git a/src/resolve/resolved-resolv-conf.c b/src/resolve/resolved-resolv-conf.c deleted file mode 100644 index 801014caf5..0000000000 --- a/src/resolve/resolved-resolv-conf.c +++ /dev/null @@ -1,276 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen <teg@jklm.no> - - 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 <resolv.h> - -#include "alloc-util.h" -#include "dns-domain.h" -#include "fd-util.h" -#include "fileio-label.h" -#include "fileio.h" -#include "ordered-set.h" -#include "resolved-conf.h" -#include "resolved-resolv-conf.h" -#include "string-util.h" -#include "strv.h" - -int manager_read_resolv_conf(Manager *m) { - _cleanup_fclose_ FILE *f = NULL; - struct stat st, own; - char line[LINE_MAX]; - usec_t t; - int r; - - assert(m); - - /* Reads the system /etc/resolv.conf, if it exists and is not - * symlinked to our own resolv.conf instance */ - - if (!m->read_resolv_conf) - return 0; - - r = stat("/etc/resolv.conf", &st); - if (r < 0) { - if (errno == ENOENT) - return 0; - - r = log_warning_errno(errno, "Failed to stat /etc/resolv.conf: %m"); - goto clear; - } - - /* Have we already seen the file? */ - t = timespec_load(&st.st_mtim); - if (t == m->resolv_conf_mtime) - return 0; - - /* Is it symlinked to our own file? */ - if (stat("/run/systemd/resolve/resolv.conf", &own) >= 0 && - st.st_dev == own.st_dev && - st.st_ino == own.st_ino) - return 0; - - f = fopen("/etc/resolv.conf", "re"); - if (!f) { - if (errno == ENOENT) - return 0; - - r = log_warning_errno(errno, "Failed to open /etc/resolv.conf: %m"); - goto clear; - } - - if (fstat(fileno(f), &st) < 0) { - r = log_error_errno(errno, "Failed to stat open file: %m"); - goto clear; - } - - dns_server_mark_all(m->dns_servers); - dns_search_domain_mark_all(m->search_domains); - - FOREACH_LINE(line, f, r = -errno; goto clear) { - const char *a; - char *l; - - l = strstrip(line); - if (*l == '#' || *l == ';') - continue; - - a = first_word(l, "nameserver"); - if (a) { - r = manager_parse_dns_server_string_and_warn(m, DNS_SERVER_SYSTEM, a); - if (r < 0) - log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring.", a); - - continue; - } - - a = first_word(l, "domain"); - if (!a) /* We treat "domain" lines, and "search" lines as equivalent, and add both to our list. */ - a = first_word(l, "search"); - if (a) { - r = manager_parse_search_domains_and_warn(m, a); - if (r < 0) - log_warning_errno(r, "Failed to parse search domain string '%s', ignoring.", a); - } - } - - m->resolv_conf_mtime = t; - - /* Flush out all servers and search domains that are still - * marked. Those are then ones that didn't appear in the new - * /etc/resolv.conf */ - dns_server_unlink_marked(m->dns_servers); - dns_search_domain_unlink_marked(m->search_domains); - - /* Whenever /etc/resolv.conf changes, start using the first - * DNS server of it. This is useful to deal with broken - * network managing implementations (like NetworkManager), - * that when connecting to a VPN place both the VPN DNS - * servers and the local ones in /etc/resolv.conf. Without - * resetting the DNS server to use back to the first entry we - * will continue to use the local one thus being unable to - * resolve VPN domains. */ - manager_set_dns_server(m, m->dns_servers); - - /* Unconditionally flush the cache when /etc/resolv.conf is - * modified, even if the data it contained was completely - * identical to the previous version we used. We do this - * because altering /etc/resolv.conf is typically done when - * the network configuration changes, and that should be - * enough to flush the global unicast DNS cache. */ - if (m->unicast_scope) - dns_cache_flush(&m->unicast_scope->cache); - - return 0; - -clear: - dns_server_unlink_all(m->dns_servers); - dns_search_domain_unlink_all(m->search_domains); - return r; -} - -static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) { - assert(s); - assert(f); - assert(count); - - if (!dns_server_string(s)) { - log_warning("Our of memory, or invalid DNS address. Ignoring server."); - return; - } - - /* Check if the DNS server is limited to particular domains; - * resolv.conf does not have a syntax to express that, so it must not - * appear as a global name server to avoid routing unrelated domains to - * it (which is a privacy violation, will most probably fail anyway, - * and adds unnecessary load) */ - if (dns_server_limited_domains(s)) { - log_debug("DNS server %s has route-only domains, not using as global name server", dns_server_string(s)); - return; - } - - if (*count == MAXNS) - fputs("# Too many DNS servers configured, the following entries may be ignored.\n", f); - (*count)++; - - fprintf(f, "nameserver %s\n", dns_server_string(s)); -} - -static void write_resolv_conf_search( - OrderedSet *domains, - FILE *f) { - unsigned length = 0, count = 0; - Iterator i; - char *domain; - - assert(domains); - assert(f); - - fputs("search", f); - - ORDERED_SET_FOREACH(domain, domains, i) { - if (++count > MAXDNSRCH) { - fputs("\n# Too many search domains configured, remaining ones ignored.", f); - break; - } - length += strlen(domain) + 1; - if (length > 256) { - fputs("\n# Total length of all search domains is too long, remaining ones ignored.", f); - break; - } - fputc(' ', f); - fputs(domain, f); - } - - fputs("\n", f); -} - -static int write_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *domains) { - Iterator i; - - fputs("# This file is managed by systemd-resolved(8). Do not edit.\n#\n" - "# This is a dynamic resolv.conf file for connecting local clients directly to\n" - "# all known DNS servers.\n#\n" - "# Third party programs must not access this file directly, but only through the\n" - "# symlink at /etc/resolv.conf. To manage resolv.conf(5) in a different way,\n" - "# replace this symlink by a static file or a different symlink.\n#\n" - "# See systemd-resolved.service(8) for details about the supported modes of\n" - "# operation for /etc/resolv.conf.\n\n", f); - - if (ordered_set_isempty(dns)) - fputs("# No DNS servers known.\n", f); - else { - unsigned count = 0; - DnsServer *s; - - ORDERED_SET_FOREACH(s, dns, i) - write_resolv_conf_server(s, f, &count); - } - - if (!ordered_set_isempty(domains)) - write_resolv_conf_search(domains, f); - - return fflush_and_check(f); -} - -int manager_write_resolv_conf(Manager *m) { - - _cleanup_ordered_set_free_ OrderedSet *dns = NULL, *domains = NULL; - _cleanup_free_ char *temp_path = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(m); - - /* Read the system /etc/resolv.conf first */ - (void) manager_read_resolv_conf(m); - - /* Add the full list to a set, to filter out duplicates */ - r = manager_compile_dns_servers(m, &dns); - if (r < 0) - return log_warning_errno(r, "Failed to compile list of DNS servers: %m"); - - r = manager_compile_search_domains(m, &domains, false); - if (r < 0) - return log_warning_errno(r, "Failed to compile list of search domains: %m"); - - r = fopen_temporary_label(PRIVATE_RESOLV_CONF, PRIVATE_RESOLV_CONF, &f, &temp_path); - if (r < 0) - return log_warning_errno(r, "Failed to open private resolv.conf file for writing: %m"); - - (void) fchmod(fileno(f), 0644); - - r = write_resolv_conf_contents(f, dns, domains); - if (r < 0) { - log_error_errno(r, "Failed to write private resolv.conf contents: %m"); - goto fail; - } - - if (rename(temp_path, PRIVATE_RESOLV_CONF) < 0) { - r = log_error_errno(errno, "Failed to move private resolv.conf file into place: %m"); - goto fail; - } - - return 0; - -fail: - (void) unlink(PRIVATE_RESOLV_CONF); - (void) unlink(temp_path); - - return r; -} diff --git a/src/resolve/resolved-resolv-conf.h b/src/resolve/resolved-resolv-conf.h deleted file mode 100644 index 75fa080e4c..0000000000 --- a/src/resolve/resolved-resolv-conf.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen <teg@jklm.no> - - 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" - -#define PRIVATE_RESOLV_CONF "/run/systemd/resolve/resolv.conf" - -int manager_read_resolv_conf(Manager *m); -int manager_write_resolv_conf(Manager *m); diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c deleted file mode 100644 index deb75f9ae5..0000000000 --- a/src/resolve/resolved.c +++ /dev/null @@ -1,120 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen <teg@jklm.no> - - 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 "sd-daemon.h" -#include "sd-event.h" - -#include "capability-util.h" -#include "mkdir.h" -#include "resolved-conf.h" -#include "resolved-manager.h" -#include "resolved-resolv-conf.h" -#include "selinux-util.h" -#include "signal-util.h" -#include "user-util.h" - -int main(int argc, char *argv[]) { - _cleanup_(manager_freep) Manager *m = NULL; - const char *user = "systemd-resolve"; - uid_t uid; - gid_t gid; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - if (argc != 1) { - log_error("This program takes no arguments."); - r = -EINVAL; - goto finish; - } - - umask(0022); - - r = mac_selinux_init(); - if (r < 0) { - log_error_errno(r, "SELinux setup failed: %m"); - goto finish; - } - - r = get_user_creds(&user, &uid, &gid, NULL, NULL); - if (r < 0) { - log_error_errno(r, "Cannot resolve user name %s: %m", user); - goto finish; - } - - /* Always create the directory where resolv.conf will live */ - r = mkdir_safe_label("/run/systemd/resolve", 0755, uid, gid); - if (r < 0) { - log_error_errno(r, "Could not create runtime directory: %m"); - goto finish; - } - - /* Drop privileges, but keep three caps. Note that we drop those too, later on (see below) */ - r = drop_privileges(uid, gid, - (UINT64_C(1) << CAP_NET_RAW)| /* needed for SO_BINDTODEVICE */ - (UINT64_C(1) << CAP_NET_BIND_SERVICE)| /* needed to bind on port 53 */ - (UINT64_C(1) << CAP_SETPCAP) /* needed in order to drop the caps later */); - if (r < 0) - goto finish; - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGUSR1, SIGUSR2, -1) >= 0); - - r = manager_new(&m); - if (r < 0) { - log_error_errno(r, "Could not create manager: %m"); - goto finish; - } - - r = manager_start(m); - if (r < 0) { - log_error_errno(r, "Failed to start manager: %m"); - goto finish; - } - - /* Write finish default resolv.conf to avoid a dangling symlink */ - (void) manager_write_resolv_conf(m); - - /* Let's drop the remaining caps now */ - r = capability_bounding_set_drop(0, true); - if (r < 0) { - log_error_errno(r, "Failed to drop remaining caps: %m"); - goto finish; - } - - sd_notify(false, - "READY=1\n" - "STATUS=Processing requests..."); - - r = sd_event_loop(m->event); - if (r < 0) { - log_error_errno(r, "Event loop failed: %m"); - goto finish; - } - - sd_event_get_exit_code(m->event, &r); - -finish: - sd_notify(false, - "STOPPING=1\n" - "STATUS=Shutting down..."); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/resolve/resolved.conf.in b/src/resolve/resolved.conf.in deleted file mode 100644 index 60afa151e3..0000000000 --- a/src/resolve/resolved.conf.in +++ /dev/null @@ -1,21 +0,0 @@ -# This file is part of systemd. -# -# 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. -# -# Entries in this file show the compile time defaults. -# You can change settings by editing this file. -# Defaults can be restored by simply deleting this file. -# -# See resolved.conf(5) for details - -[Resolve] -#DNS= -#FallbackDNS=@DNS_SERVERS@ -#Domains= -#LLMNR=yes -#DNSSEC=@DEFAULT_DNSSEC_MODE@ -#Cache=yes -#DNSStubListener=udp diff --git a/src/resolve/test-data/_443._tcp.fedoraproject.org.pkts b/src/resolve/test-data/_443._tcp.fedoraproject.org.pkts Binary files differdeleted file mode 100644 index a383c6286d..0000000000 --- a/src/resolve/test-data/_443._tcp.fedoraproject.org.pkts +++ /dev/null diff --git a/src/resolve/test-data/_openpgpkey.fedoraproject.org.pkts b/src/resolve/test-data/_openpgpkey.fedoraproject.org.pkts Binary files differdeleted file mode 100644 index 15de02e997..0000000000 --- a/src/resolve/test-data/_openpgpkey.fedoraproject.org.pkts +++ /dev/null diff --git a/src/resolve/test-data/fake-caa.pkts b/src/resolve/test-data/fake-caa.pkts Binary files differdeleted file mode 100644 index 1c3ecc5491..0000000000 --- a/src/resolve/test-data/fake-caa.pkts +++ /dev/null diff --git a/src/resolve/test-data/fedoraproject.org.pkts b/src/resolve/test-data/fedoraproject.org.pkts Binary files differdeleted file mode 100644 index 17874844d9..0000000000 --- a/src/resolve/test-data/fedoraproject.org.pkts +++ /dev/null diff --git a/src/resolve/test-data/gandi.net.pkts b/src/resolve/test-data/gandi.net.pkts Binary files differdeleted file mode 100644 index 5ef51e0c8e..0000000000 --- a/src/resolve/test-data/gandi.net.pkts +++ /dev/null diff --git a/src/resolve/test-data/google.com.pkts b/src/resolve/test-data/google.com.pkts Binary files differdeleted file mode 100644 index f98c4cd855..0000000000 --- a/src/resolve/test-data/google.com.pkts +++ /dev/null diff --git a/src/resolve/test-data/kyhwana.org.pkts b/src/resolve/test-data/kyhwana.org.pkts Binary files differdeleted file mode 100644 index e28a725c9a..0000000000 --- a/src/resolve/test-data/kyhwana.org.pkts +++ /dev/null diff --git a/src/resolve/test-data/root.pkts b/src/resolve/test-data/root.pkts Binary files differdeleted file mode 100644 index 54ba668c75..0000000000 --- a/src/resolve/test-data/root.pkts +++ /dev/null diff --git a/src/resolve/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts b/src/resolve/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts Binary files differdeleted file mode 100644 index a854249532..0000000000 --- a/src/resolve/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts +++ /dev/null diff --git a/src/resolve/test-data/teamits.com.pkts b/src/resolve/test-data/teamits.com.pkts Binary files differdeleted file mode 100644 index 11deb39677..0000000000 --- a/src/resolve/test-data/teamits.com.pkts +++ /dev/null diff --git a/src/resolve/test-data/zbyszek@fedoraproject.org.pkts b/src/resolve/test-data/zbyszek@fedoraproject.org.pkts Binary files differdeleted file mode 100644 index f0a6f982df..0000000000 --- a/src/resolve/test-data/zbyszek@fedoraproject.org.pkts +++ /dev/null diff --git a/src/resolve/test-dns-packet.c b/src/resolve/test-dns-packet.c deleted file mode 100644 index 956b155872..0000000000 --- a/src/resolve/test-dns-packet.c +++ /dev/null @@ -1,132 +0,0 @@ -/*** - This file is part of systemd - - Copyright 2016 Zbigniew Jędrzejewski-Szmek - - 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 <net/if.h> -#include <glob.h> - -#include "alloc-util.h" -#include "fileio.h" -#include "glob-util.h" -#include "log.h" -#include "macro.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-rr.h" -#include "string-util.h" -#include "strv.h" -#include "unaligned.h" - -#define HASH_KEY SD_ID128_MAKE(d3,1e,48,90,4b,fa,4c,fe,af,9d,d5,a1,d7,2e,8a,b1) - -static void verify_rr_copy(DnsResourceRecord *rr) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *copy = NULL; - const char *a, *b; - - assert_se(copy = dns_resource_record_copy(rr)); - assert_se(dns_resource_record_equal(copy, rr) > 0); - - assert_se(a = dns_resource_record_to_string(rr)); - assert_se(b = dns_resource_record_to_string(copy)); - - assert_se(streq(a, b)); -} - -static uint64_t hash(DnsResourceRecord *rr) { - struct siphash state; - - siphash24_init(&state, HASH_KEY.bytes); - dns_resource_record_hash_func(rr, &state); - return siphash24_finalize(&state); -} - -static void test_packet_from_file(const char* filename, bool canonical) { - _cleanup_free_ char *data = NULL; - size_t data_size, packet_size, offset; - - assert_se(read_full_file(filename, &data, &data_size) >= 0); - assert_se(data); - assert_se(data_size > 8); - - log_info("============== %s %s==============", filename, canonical ? "canonical " : ""); - - for (offset = 0; offset < data_size; offset += 8 + packet_size) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL, *p2 = NULL; - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL, *rr2 = NULL; - const char *s, *s2; - uint64_t hash1, hash2; - - packet_size = unaligned_read_le64(data + offset); - assert_se(packet_size > 0); - assert_se(offset + 8 + packet_size <= data_size); - - assert_se(dns_packet_new(&p, DNS_PROTOCOL_DNS, 0) >= 0); - - assert_se(dns_packet_append_blob(p, data + offset + 8, packet_size, NULL) >= 0); - assert_se(dns_packet_read_rr(p, &rr, NULL, NULL) >= 0); - - verify_rr_copy(rr); - - s = dns_resource_record_to_string(rr); - assert_se(s); - puts(s); - - hash1 = hash(rr); - - assert_se(dns_resource_record_to_wire_format(rr, canonical) >= 0); - - assert_se(dns_packet_new(&p2, DNS_PROTOCOL_DNS, 0) >= 0); - assert_se(dns_packet_append_blob(p2, rr->wire_format, rr->wire_format_size, NULL) >= 0); - assert_se(dns_packet_read_rr(p2, &rr2, NULL, NULL) >= 0); - - verify_rr_copy(rr); - - s2 = dns_resource_record_to_string(rr); - assert_se(s2); - assert_se(streq(s, s2)); - - hash2 = hash(rr); - assert_se(hash1 == hash2); - } -} - -int main(int argc, char **argv) { - int i, N; - _cleanup_globfree_ glob_t g = {}; - char **fnames; - - log_parse_environment(); - - if (argc >= 2) { - N = argc - 1; - fnames = argv + 1; - } else { - assert_se(glob(RESOLVE_TEST_DIR "/*.pkts", GLOB_NOSORT, NULL, &g) == 0); - N = g.gl_pathc; - fnames = g.gl_pathv; - } - - for (i = 0; i < N; i++) { - test_packet_from_file(fnames[i], false); - puts(""); - test_packet_from_file(fnames[i], true); - if (i + 1 < N) - puts(""); - } - - return EXIT_SUCCESS; -} diff --git a/src/resolve/test-dnssec-complex.c b/src/resolve/test-dnssec-complex.c deleted file mode 100644 index 58c089eb40..0000000000 --- a/src/resolve/test-dnssec-complex.c +++ /dev/null @@ -1,236 +0,0 @@ -/*** - 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 <netinet/ip.h> - -#include "sd-bus.h" - -#include "af-list.h" -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "dns-type.h" -#include "random-util.h" -#include "string-util.h" -#include "time-util.h" - -#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) - -static void prefix_random(const char *name, char **ret) { - uint64_t i, u; - char *m = NULL; - - u = 1 + (random_u64() & 3); - - for (i = 0; i < u; i++) { - _cleanup_free_ char *b = NULL; - char *x; - - assert_se(asprintf(&b, "x%" PRIu64 "x", random_u64())); - x = strjoin(b, ".", name, NULL); - assert_se(x); - - free(m); - m = x; - } - - *ret = m; - } - -static void test_rr_lookup(sd_bus *bus, const char *name, uint16_t type, const char *result) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *m = NULL; - int r; - - /* If the name starts with a dot, we prefix one to three random labels */ - if (startswith(name, ".")) { - prefix_random(name + 1, &m); - name = m; - } - - assert_se(sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveRecord") >= 0); - - assert_se(sd_bus_message_append(req, "isqqt", 0, name, DNS_CLASS_IN, type, UINT64_C(0)) >= 0); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - - if (r < 0) { - assert_se(result); - assert_se(sd_bus_error_has_name(&error, result)); - log_info("[OK] %s/%s resulted in <%s>.", name, dns_type_to_string(type), error.name); - } else { - assert_se(!result); - log_info("[OK] %s/%s succeeded.", name, dns_type_to_string(type)); - } -} - -static void test_hostname_lookup(sd_bus *bus, const char *name, int family, const char *result) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *m = NULL; - const char *af; - int r; - - af = family == AF_UNSPEC ? "AF_UNSPEC" : af_to_name(family); - - /* If the name starts with a dot, we prefix one to three random labels */ - if (startswith(name, ".")) { - prefix_random(name + 1, &m); - name = m; - } - - assert_se(sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveHostname") >= 0); - - assert_se(sd_bus_message_append(req, "isit", 0, name, family, UINT64_C(0)) >= 0); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - - if (r < 0) { - assert_se(result); - assert_se(sd_bus_error_has_name(&error, result)); - log_info("[OK] %s/%s resulted in <%s>.", name, af, error.name); - } else { - assert_se(!result); - log_info("[OK] %s/%s succeeded.", name, af); - } - -} - -int main(int argc, char* argv[]) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - - /* Note that this is a manual test as it requires: - * - * Full network access - * A DNSSEC capable DNS server - * That zones contacted are still set up as they were when I wrote this. - */ - - assert_se(sd_bus_open_system(&bus) >= 0); - - /* Normally signed */ - test_rr_lookup(bus, "www.eurid.eu", DNS_TYPE_A, NULL); - test_hostname_lookup(bus, "www.eurid.eu", AF_UNSPEC, NULL); - - test_rr_lookup(bus, "sigok.verteiltesysteme.net", DNS_TYPE_A, NULL); - test_hostname_lookup(bus, "sigok.verteiltesysteme.net", AF_UNSPEC, NULL); - - /* Normally signed, NODATA */ - test_rr_lookup(bus, "www.eurid.eu", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); - test_rr_lookup(bus, "sigok.verteiltesysteme.net", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); - - /* Invalid signature */ - test_rr_lookup(bus, "sigfail.verteiltesysteme.net", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED); - test_hostname_lookup(bus, "sigfail.verteiltesysteme.net", AF_INET, BUS_ERROR_DNSSEC_FAILED); - - /* Invalid signature, RSA, wildcard */ - test_rr_lookup(bus, ".wilda.rhybar.0skar.cz", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED); - test_hostname_lookup(bus, ".wilda.rhybar.0skar.cz", AF_INET, BUS_ERROR_DNSSEC_FAILED); - - /* Invalid signature, ECDSA, wildcard */ - test_rr_lookup(bus, ".wilda.rhybar.ecdsa.0skar.cz", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED); - test_hostname_lookup(bus, ".wilda.rhybar.ecdsa.0skar.cz", AF_INET, BUS_ERROR_DNSSEC_FAILED); - - /* NXDOMAIN in NSEC domain */ - test_rr_lookup(bus, "hhh.nasa.gov", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, "hhh.nasa.gov", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); - - /* wildcard, NSEC zone */ - test_rr_lookup(bus, ".wilda.nsec.0skar.cz", DNS_TYPE_A, NULL); - test_hostname_lookup(bus, ".wilda.nsec.0skar.cz", AF_INET, NULL); - - /* wildcard, NSEC zone, NODATA */ - test_rr_lookup(bus, ".wilda.nsec.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); - - /* wildcard, NSEC3 zone */ - test_rr_lookup(bus, ".wilda.0skar.cz", DNS_TYPE_A, NULL); - test_hostname_lookup(bus, ".wilda.0skar.cz", AF_INET, NULL); - - /* wildcard, NSEC3 zone, NODATA */ - test_rr_lookup(bus, ".wilda.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); - - /* wildcard, NSEC zone, CNAME */ - test_rr_lookup(bus, ".wild.nsec.0skar.cz", DNS_TYPE_A, NULL); - test_hostname_lookup(bus, ".wild.nsec.0skar.cz", AF_UNSPEC, NULL); - test_hostname_lookup(bus, ".wild.nsec.0skar.cz", AF_INET, NULL); - - /* wildcard, NSEC zone, NODATA, CNAME */ - test_rr_lookup(bus, ".wild.nsec.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); - - /* wildcard, NSEC3 zone, CNAME */ - test_rr_lookup(bus, ".wild.0skar.cz", DNS_TYPE_A, NULL); - test_hostname_lookup(bus, ".wild.0skar.cz", AF_UNSPEC, NULL); - test_hostname_lookup(bus, ".wild.0skar.cz", AF_INET, NULL); - - /* wildcard, NSEC3 zone, NODATA, CNAME */ - test_rr_lookup(bus, ".wild.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); - - /* NODATA due to empty non-terminal in NSEC domain */ - test_rr_lookup(bus, "herndon.nasa.gov", DNS_TYPE_A, BUS_ERROR_NO_SUCH_RR); - test_hostname_lookup(bus, "herndon.nasa.gov", AF_UNSPEC, BUS_ERROR_NO_SUCH_RR); - test_hostname_lookup(bus, "herndon.nasa.gov", AF_INET, BUS_ERROR_NO_SUCH_RR); - test_hostname_lookup(bus, "herndon.nasa.gov", AF_INET6, BUS_ERROR_NO_SUCH_RR); - - /* NXDOMAIN in NSEC root zone: */ - test_rr_lookup(bus, "jasdhjas.kjkfgjhfjg", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_INET, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN"); - - /* NXDOMAIN in NSEC3 .com zone: */ - test_rr_lookup(bus, "kjkfgjhfjgsdfdsfd.com", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_INET, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); - - /* Unsigned A */ - test_rr_lookup(bus, "poettering.de", DNS_TYPE_A, NULL); - test_rr_lookup(bus, "poettering.de", DNS_TYPE_AAAA, NULL); - test_hostname_lookup(bus, "poettering.de", AF_UNSPEC, NULL); - test_hostname_lookup(bus, "poettering.de", AF_INET, NULL); - test_hostname_lookup(bus, "poettering.de", AF_INET6, NULL); - -#ifdef HAVE_LIBIDN - /* Unsigned A with IDNA conversion necessary */ - test_hostname_lookup(bus, "pöttering.de", AF_UNSPEC, NULL); - test_hostname_lookup(bus, "pöttering.de", AF_INET, NULL); - test_hostname_lookup(bus, "pöttering.de", AF_INET6, NULL); -#endif - - /* DNAME, pointing to NXDOMAIN */ - test_rr_lookup(bus, ".ireallyhpoethisdoesnexist.xn--kprw13d.", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); - test_rr_lookup(bus, ".ireallyhpoethisdoesnexist.xn--kprw13d.", DNS_TYPE_RP, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_INET, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN"); - - return 0; -} diff --git a/src/resolve/test-dnssec.c b/src/resolve/test-dnssec.c deleted file mode 100644 index b3018e8239..0000000000 --- a/src/resolve/test-dnssec.c +++ /dev/null @@ -1,343 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 <arpa/inet.h> -#include <netinet/in.h> -#include <sys/socket.h> - -#include "alloc-util.h" -#include "resolved-dns-dnssec.h" -#include "resolved-dns-rr.h" -#include "string-util.h" -#include "hexdecoct.h" - -static void test_dnssec_canonicalize_one(const char *original, const char *canonical, int r) { - char canonicalized[DNSSEC_CANONICAL_HOSTNAME_MAX]; - - assert_se(dnssec_canonicalize(original, canonicalized, sizeof(canonicalized)) == r); - if (r < 0) - return; - - assert_se(streq(canonicalized, canonical)); -} - -static void test_dnssec_canonicalize(void) { - test_dnssec_canonicalize_one("", ".", 1); - test_dnssec_canonicalize_one(".", ".", 1); - test_dnssec_canonicalize_one("foo", "foo.", 4); - test_dnssec_canonicalize_one("foo.", "foo.", 4); - test_dnssec_canonicalize_one("FOO.", "foo.", 4); - test_dnssec_canonicalize_one("FOO.bar.", "foo.bar.", 8); - test_dnssec_canonicalize_one("FOO..bar.", NULL, -EINVAL); -} - -#ifdef HAVE_GCRYPT - -static void test_dnssec_verify_dns_key(void) { - - static const uint8_t ds1_fprint[] = { - 0x46, 0x8B, 0xC8, 0xDD, 0xC7, 0xE8, 0x27, 0x03, 0x40, 0xBB, 0x8A, 0x1F, 0x3B, 0x2E, 0x45, 0x9D, - 0x80, 0x67, 0x14, 0x01, - }; - static const uint8_t ds2_fprint[] = { - 0x8A, 0xEE, 0x80, 0x47, 0x05, 0x5F, 0x83, 0xD1, 0x48, 0xBA, 0x8F, 0xF6, 0xDD, 0xA7, 0x60, 0xCE, - 0x94, 0xF7, 0xC7, 0x5E, 0x52, 0x4C, 0xF2, 0xE9, 0x50, 0xB9, 0x2E, 0xCB, 0xEF, 0x96, 0xB9, 0x98, - }; - static const uint8_t dnskey_blob[] = { - 0x03, 0x01, 0x00, 0x01, 0xa8, 0x12, 0xda, 0x4f, 0xd2, 0x7d, 0x54, 0x14, 0x0e, 0xcc, 0x5b, 0x5e, - 0x45, 0x9c, 0x96, 0x98, 0xc0, 0xc0, 0x85, 0x81, 0xb1, 0x47, 0x8c, 0x7d, 0xe8, 0x39, 0x50, 0xcc, - 0xc5, 0xd0, 0xf2, 0x00, 0x81, 0x67, 0x79, 0xf6, 0xcc, 0x9d, 0xad, 0x6c, 0xbb, 0x7b, 0x6f, 0x48, - 0x97, 0x15, 0x1c, 0xfd, 0x0b, 0xfe, 0xd3, 0xd7, 0x7d, 0x9f, 0x81, 0x26, 0xd3, 0xc5, 0x65, 0x49, - 0xcf, 0x46, 0x62, 0xb0, 0x55, 0x6e, 0x47, 0xc7, 0x30, 0xef, 0x51, 0xfb, 0x3e, 0xc6, 0xef, 0xde, - 0x27, 0x3f, 0xfa, 0x57, 0x2d, 0xa7, 0x1d, 0x80, 0x46, 0x9a, 0x5f, 0x14, 0xb3, 0xb0, 0x2c, 0xbe, - 0x72, 0xca, 0xdf, 0xb2, 0xff, 0x36, 0x5b, 0x4f, 0xec, 0x58, 0x8e, 0x8d, 0x01, 0xe9, 0xa9, 0xdf, - 0xb5, 0x60, 0xad, 0x52, 0x4d, 0xfc, 0xa9, 0x3e, 0x8d, 0x35, 0x95, 0xb3, 0x4e, 0x0f, 0xca, 0x45, - 0x1b, 0xf7, 0xef, 0x3a, 0x88, 0x25, 0x08, 0xc7, 0x4e, 0x06, 0xc1, 0x62, 0x1a, 0xce, 0xd8, 0x77, - 0xbd, 0x02, 0x65, 0xf8, 0x49, 0xfb, 0xce, 0xf6, 0xa8, 0x09, 0xfc, 0xde, 0xb2, 0x09, 0x9d, 0x39, - 0xf8, 0x63, 0x9c, 0x32, 0x42, 0x7c, 0xa0, 0x30, 0x86, 0x72, 0x7a, 0x4a, 0xc6, 0xd4, 0xb3, 0x2d, - 0x24, 0xef, 0x96, 0x3f, 0xc2, 0xda, 0xd3, 0xf2, 0x15, 0x6f, 0xda, 0x65, 0x4b, 0x81, 0x28, 0x68, - 0xf4, 0xfe, 0x3e, 0x71, 0x4f, 0x50, 0x96, 0x72, 0x58, 0xa1, 0x89, 0xdd, 0x01, 0x61, 0x39, 0x39, - 0xc6, 0x76, 0xa4, 0xda, 0x02, 0x70, 0x3d, 0xc0, 0xdc, 0x8d, 0x70, 0x72, 0x04, 0x90, 0x79, 0xd4, - 0xec, 0x65, 0xcf, 0x49, 0x35, 0x25, 0x3a, 0x14, 0x1a, 0x45, 0x20, 0xeb, 0x31, 0xaf, 0x92, 0xba, - 0x20, 0xd3, 0xcd, 0xa7, 0x13, 0x44, 0xdc, 0xcf, 0xf0, 0x27, 0x34, 0xb9, 0xe7, 0x24, 0x6f, 0x73, - 0xe7, 0xea, 0x77, 0x03, - }; - - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *dnskey = NULL, *ds1 = NULL, *ds2 = NULL; - - /* The two DS RRs in effect for nasa.gov on 2015-12-01. */ - ds1 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "nasa.gov"); - assert_se(ds1); - - ds1->ds.key_tag = 47857; - ds1->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; - ds1->ds.digest_type = DNSSEC_DIGEST_SHA1; - ds1->ds.digest_size = sizeof(ds1_fprint); - ds1->ds.digest = memdup(ds1_fprint, ds1->ds.digest_size); - assert_se(ds1->ds.digest); - - log_info("DS1: %s", strna(dns_resource_record_to_string(ds1))); - - ds2 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "NASA.GOV"); - assert_se(ds2); - - ds2->ds.key_tag = 47857; - ds2->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; - ds2->ds.digest_type = DNSSEC_DIGEST_SHA256; - ds2->ds.digest_size = sizeof(ds2_fprint); - ds2->ds.digest = memdup(ds2_fprint, ds2->ds.digest_size); - assert_se(ds2->ds.digest); - - log_info("DS2: %s", strna(dns_resource_record_to_string(ds2))); - - dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nasa.GOV"); - assert_se(dnskey); - - dnskey->dnskey.flags = 257; - dnskey->dnskey.protocol = 3; - dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; - dnskey->dnskey.key_size = sizeof(dnskey_blob); - dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); - assert_se(dnskey->dnskey.key); - - log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); - log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); - - assert_se(dnssec_verify_dnskey_by_ds(dnskey, ds1, false) > 0); - assert_se(dnssec_verify_dnskey_by_ds(dnskey, ds2, false) > 0); -} - -static void test_dnssec_verify_rrset(void) { - - static const uint8_t signature_blob[] = { - 0x7f, 0x79, 0xdd, 0x5e, 0x89, 0x79, 0x18, 0xd0, 0x34, 0x86, 0x8c, 0x72, 0x77, 0x75, 0x48, 0x4d, - 0xc3, 0x7d, 0x38, 0x04, 0xab, 0xcd, 0x9e, 0x4c, 0x82, 0xb0, 0x92, 0xca, 0xe9, 0x66, 0xe9, 0x6e, - 0x47, 0xc7, 0x68, 0x8c, 0x94, 0xf6, 0x69, 0xcb, 0x75, 0x94, 0xe6, 0x30, 0xa6, 0xfb, 0x68, 0x64, - 0x96, 0x1a, 0x84, 0xe1, 0xdc, 0x16, 0x4c, 0x83, 0x6c, 0x44, 0xf2, 0x74, 0x4d, 0x74, 0x79, 0x8f, - 0xf3, 0xf4, 0x63, 0x0d, 0xef, 0x5a, 0xe7, 0xe2, 0xfd, 0xf2, 0x2b, 0x38, 0x7c, 0x28, 0x96, 0x9d, - 0xb6, 0xcd, 0x5c, 0x3b, 0x57, 0xe2, 0x24, 0x78, 0x65, 0xd0, 0x9e, 0x77, 0x83, 0x09, 0x6c, 0xff, - 0x3d, 0x52, 0x3f, 0x6e, 0xd1, 0xed, 0x2e, 0xf9, 0xee, 0x8e, 0xa6, 0xbe, 0x9a, 0xa8, 0x87, 0x76, - 0xd8, 0x77, 0xcc, 0x96, 0xa0, 0x98, 0xa1, 0xd1, 0x68, 0x09, 0x43, 0xcf, 0x56, 0xd9, 0xd1, 0x66, - }; - - static const uint8_t dnskey_blob[] = { - 0x03, 0x01, 0x00, 0x01, 0x9b, 0x49, 0x9b, 0xc1, 0xf9, 0x9a, 0xe0, 0x4e, 0xcf, 0xcb, 0x14, 0x45, - 0x2e, 0xc9, 0xf9, 0x74, 0xa7, 0x18, 0xb5, 0xf3, 0xde, 0x39, 0x49, 0xdf, 0x63, 0x33, 0x97, 0x52, - 0xe0, 0x8e, 0xac, 0x50, 0x30, 0x8e, 0x09, 0xd5, 0x24, 0x3d, 0x26, 0xa4, 0x49, 0x37, 0x2b, 0xb0, - 0x6b, 0x1b, 0xdf, 0xde, 0x85, 0x83, 0xcb, 0x22, 0x4e, 0x60, 0x0a, 0x91, 0x1a, 0x1f, 0xc5, 0x40, - 0xb1, 0xc3, 0x15, 0xc1, 0x54, 0x77, 0x86, 0x65, 0x53, 0xec, 0x10, 0x90, 0x0c, 0x91, 0x00, 0x5e, - 0x15, 0xdc, 0x08, 0x02, 0x4c, 0x8c, 0x0d, 0xc0, 0xac, 0x6e, 0xc4, 0x3e, 0x1b, 0x80, 0x19, 0xe4, - 0xf7, 0x5f, 0x77, 0x51, 0x06, 0x87, 0x61, 0xde, 0xa2, 0x18, 0x0f, 0x40, 0x8b, 0x79, 0x72, 0xfa, - 0x8d, 0x1a, 0x44, 0x47, 0x0d, 0x8e, 0x3a, 0x2d, 0xc7, 0x39, 0xbf, 0x56, 0x28, 0x97, 0xd9, 0x20, - 0x4f, 0x00, 0x51, 0x3b, - }; - - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *a = NULL, *rrsig = NULL, *dnskey = NULL; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - DnssecResult result; - - a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "nAsA.gov"); - assert_se(a); - - a->a.in_addr.s_addr = inet_addr("52.0.14.116"); - - log_info("A: %s", strna(dns_resource_record_to_string(a))); - - rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV."); - assert_se(rrsig); - - rrsig->rrsig.type_covered = DNS_TYPE_A; - rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256; - rrsig->rrsig.labels = 2; - rrsig->rrsig.original_ttl = 600; - rrsig->rrsig.expiration = 0x5683135c; - rrsig->rrsig.inception = 0x565b7da8; - rrsig->rrsig.key_tag = 63876; - rrsig->rrsig.signer = strdup("Nasa.Gov."); - assert_se(rrsig->rrsig.signer); - rrsig->rrsig.signature_size = sizeof(signature_blob); - rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size); - assert_se(rrsig->rrsig.signature); - - log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig))); - - dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV"); - assert_se(dnskey); - - dnskey->dnskey.flags = 256; - dnskey->dnskey.protocol = 3; - dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; - dnskey->dnskey.key_size = sizeof(dnskey_blob); - dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); - assert_se(dnskey->dnskey.key); - - log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); - log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); - - assert_se(dnssec_key_match_rrsig(a->key, rrsig) > 0); - assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0); - - answer = dns_answer_new(1); - assert_se(answer); - assert_se(dns_answer_add(answer, a, 0, DNS_ANSWER_AUTHENTICATED) >= 0); - - /* Validate the RR as it if was 2015-12-2 today */ - assert_se(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, 1449092754*USEC_PER_SEC, &result) >= 0); - assert_se(result == DNSSEC_VALIDATED); -} - -static void test_dnssec_verify_rrset2(void) { - - static const uint8_t signature_blob[] = { - 0x48, 0x45, 0xc8, 0x8b, 0xc0, 0x14, 0x92, 0xf5, 0x15, 0xc6, 0x84, 0x9d, 0x2f, 0xe3, 0x32, 0x11, - 0x7d, 0xf1, 0xe6, 0x87, 0xb9, 0x42, 0xd3, 0x8b, 0x9e, 0xaf, 0x92, 0x31, 0x0a, 0x53, 0xad, 0x8b, - 0xa7, 0x5c, 0x83, 0x39, 0x8c, 0x28, 0xac, 0xce, 0x6e, 0x9c, 0x18, 0xe3, 0x31, 0x16, 0x6e, 0xca, - 0x38, 0x31, 0xaf, 0xd9, 0x94, 0xf1, 0x84, 0xb1, 0xdf, 0x5a, 0xc2, 0x73, 0x22, 0xf6, 0xcb, 0xa2, - 0xe7, 0x8c, 0x77, 0x0c, 0x74, 0x2f, 0xc2, 0x13, 0xb0, 0x93, 0x51, 0xa9, 0x4f, 0xae, 0x0a, 0xda, - 0x45, 0xcc, 0xfd, 0x43, 0x99, 0x36, 0x9a, 0x0d, 0x21, 0xe0, 0xeb, 0x30, 0x65, 0xd4, 0xa0, 0x27, - 0x37, 0x3b, 0xe4, 0xc1, 0xc5, 0xa1, 0x2a, 0xd1, 0x76, 0xc4, 0x7e, 0x64, 0x0e, 0x5a, 0xa6, 0x50, - 0x24, 0xd5, 0x2c, 0xcc, 0x6d, 0xe5, 0x37, 0xea, 0xbd, 0x09, 0x34, 0xed, 0x24, 0x06, 0xa1, 0x22, - }; - - static const uint8_t dnskey_blob[] = { - 0x03, 0x01, 0x00, 0x01, 0xc3, 0x7f, 0x1d, 0xd1, 0x1c, 0x97, 0xb1, 0x13, 0x34, 0x3a, 0x9a, 0xea, - 0xee, 0xd9, 0x5a, 0x11, 0x1b, 0x17, 0xc7, 0xe3, 0xd4, 0xda, 0x20, 0xbc, 0x5d, 0xba, 0x74, 0xe3, - 0x37, 0x99, 0xec, 0x25, 0xce, 0x93, 0x7f, 0xbd, 0x22, 0x73, 0x7e, 0x14, 0x71, 0xe0, 0x60, 0x07, - 0xd4, 0x39, 0x8b, 0x5e, 0xe9, 0xba, 0x25, 0xe8, 0x49, 0xe9, 0x34, 0xef, 0xfe, 0x04, 0x5c, 0xa5, - 0x27, 0xcd, 0xa9, 0xda, 0x70, 0x05, 0x21, 0xab, 0x15, 0x82, 0x24, 0xc3, 0x94, 0xf5, 0xd7, 0xb7, - 0xc4, 0x66, 0xcb, 0x32, 0x6e, 0x60, 0x2b, 0x55, 0x59, 0x28, 0x89, 0x8a, 0x72, 0xde, 0x88, 0x56, - 0x27, 0x95, 0xd9, 0xac, 0x88, 0x4f, 0x65, 0x2b, 0x68, 0xfc, 0xe6, 0x41, 0xc1, 0x1b, 0xef, 0x4e, - 0xd6, 0xc2, 0x0f, 0x64, 0x88, 0x95, 0x5e, 0xdd, 0x3a, 0x02, 0x07, 0x50, 0xa9, 0xda, 0xa4, 0x49, - 0x74, 0x62, 0xfe, 0xd7, - }; - - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *nsec = NULL, *rrsig = NULL, *dnskey = NULL; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - DnssecResult result; - - nsec = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC, "nasa.gov"); - assert_se(nsec); - - nsec->nsec.next_domain_name = strdup("3D-Printing.nasa.gov"); - assert_se(nsec->nsec.next_domain_name); - - nsec->nsec.types = bitmap_new(); - assert_se(nsec->nsec.types); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_A) >= 0); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NS) >= 0); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_SOA) >= 0); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_MX) >= 0); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_TXT) >= 0); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_RRSIG) >= 0); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NSEC) >= 0); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_DNSKEY) >= 0); - assert_se(bitmap_set(nsec->nsec.types, 65534) >= 0); - - log_info("NSEC: %s", strna(dns_resource_record_to_string(nsec))); - - rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV."); - assert_se(rrsig); - - rrsig->rrsig.type_covered = DNS_TYPE_NSEC; - rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256; - rrsig->rrsig.labels = 2; - rrsig->rrsig.original_ttl = 300; - rrsig->rrsig.expiration = 0x5689002f; - rrsig->rrsig.inception = 0x56617230; - rrsig->rrsig.key_tag = 30390; - rrsig->rrsig.signer = strdup("Nasa.Gov."); - assert_se(rrsig->rrsig.signer); - rrsig->rrsig.signature_size = sizeof(signature_blob); - rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size); - assert_se(rrsig->rrsig.signature); - - log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig))); - - dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV"); - assert_se(dnskey); - - dnskey->dnskey.flags = 256; - dnskey->dnskey.protocol = 3; - dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; - dnskey->dnskey.key_size = sizeof(dnskey_blob); - dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); - assert_se(dnskey->dnskey.key); - - log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); - log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); - - assert_se(dnssec_key_match_rrsig(nsec->key, rrsig) > 0); - assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0); - - answer = dns_answer_new(1); - assert_se(answer); - assert_se(dns_answer_add(answer, nsec, 0, DNS_ANSWER_AUTHENTICATED) >= 0); - - /* Validate the RR as it if was 2015-12-11 today */ - assert_se(dnssec_verify_rrset(answer, nsec->key, rrsig, dnskey, 1449849318*USEC_PER_SEC, &result) >= 0); - assert_se(result == DNSSEC_VALIDATED); -} - -static void test_dnssec_nsec3_hash(void) { - static const uint8_t salt[] = { 0xB0, 0x1D, 0xFA, 0xCE }; - static const uint8_t next_hashed_name[] = { 0x84, 0x10, 0x26, 0x53, 0xc9, 0xfa, 0x4d, 0x85, 0x6c, 0x97, 0x82, 0xe2, 0x8f, 0xdf, 0x2d, 0x5e, 0x87, 0x69, 0xc4, 0x52 }; - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - uint8_t h[DNSSEC_HASH_SIZE_MAX]; - _cleanup_free_ char *b = NULL; - int k; - - /* The NSEC3 RR for eurid.eu on 2015-12-14. */ - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC3, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM.eurid.eu."); - assert_se(rr); - - rr->nsec3.algorithm = DNSSEC_DIGEST_SHA1; - rr->nsec3.flags = 1; - rr->nsec3.iterations = 1; - rr->nsec3.salt = memdup(salt, sizeof(salt)); - assert_se(rr->nsec3.salt); - rr->nsec3.salt_size = sizeof(salt); - rr->nsec3.next_hashed_name = memdup(next_hashed_name, sizeof(next_hashed_name)); - assert_se(rr->nsec3.next_hashed_name); - rr->nsec3.next_hashed_name_size = sizeof(next_hashed_name); - - log_info("NSEC3: %s", strna(dns_resource_record_to_string(rr))); - - k = dnssec_nsec3_hash(rr, "eurid.eu", &h); - assert_se(k >= 0); - - b = base32hexmem(h, k, false); - assert_se(b); - assert_se(strcasecmp(b, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM") == 0); -} - -#endif - -int main(int argc, char*argv[]) { - - test_dnssec_canonicalize(); - -#ifdef HAVE_GCRYPT - test_dnssec_verify_dns_key(); - test_dnssec_verify_rrset(); - test_dnssec_verify_rrset2(); - test_dnssec_nsec3_hash(); -#endif - - return 0; -} diff --git a/src/resolve/test-resolve-tables.c b/src/resolve/test-resolve-tables.c deleted file mode 100644 index 2d615130e1..0000000000 --- a/src/resolve/test-resolve-tables.c +++ /dev/null @@ -1,64 +0,0 @@ -/*** - This file is part of systemd - - Copyright 2013 Zbigniew Jędrzejewski-Szmek - - 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 "dns-type.h" -#include "test-tables.h" - -int main(int argc, char **argv) { - uint16_t i; - - test_table_sparse(dns_type, DNS_TYPE); - - log_info("/* DNS_TYPE */"); - for (i = 0; i < _DNS_TYPE_MAX; i++) { - const char *s; - - s = dns_type_to_string(i); - assert_se(s == NULL || strlen(s) < _DNS_TYPE_STRING_MAX); - - if (s) - log_info("%-*s %s%s%s%s%s%s%s%s%s", - (int) _DNS_TYPE_STRING_MAX - 1, s, - dns_type_is_pseudo(i) ? "pseudo " : "", - dns_type_is_valid_query(i) ? "valid_query " : "", - dns_type_is_valid_rr(i) ? "is_valid_rr " : "", - dns_type_may_redirect(i) ? "may_redirect " : "", - dns_type_is_dnssec(i) ? "dnssec " : "", - dns_type_is_obsolete(i) ? "obsolete " : "", - dns_type_may_wildcard(i) ? "wildcard " : "", - dns_type_apex_only(i) ? "apex_only " : "", - dns_type_needs_authentication(i) ? "needs_authentication" : ""); - } - - log_info("/* DNS_CLASS */"); - for (i = 0; i < _DNS_CLASS_MAX; i++) { - const char *s; - - s = dns_class_to_string(i); - assert_se(s == NULL || strlen(s) < _DNS_CLASS_STRING_MAX); - - if (s) - log_info("%-*s %s%s", - (int) _DNS_CLASS_STRING_MAX - 1, s, - dns_class_is_pseudo(i) ? "is_pseudo " : "", - dns_class_is_valid_rr(i) ? "is_valid_rr " : ""); - } - - return EXIT_SUCCESS; -} |