diff options
author | Martin Pitt <martin.pitt@ubuntu.com> | 2014-11-20 15:28:12 +0100 |
---|---|---|
committer | Martin Pitt <martin.pitt@ubuntu.com> | 2014-11-20 15:28:12 +0100 |
commit | 5eef597e931b0428bb984dc2bf0736d032a9198c (patch) | |
tree | c24d24d36d083e227a6ba3c1b5ae5c40ae822cb2 /src/resolve | |
parent | e842803ae5f3f162fa51f30787c3b2423e3826cf (diff) | |
download | systemd-5eef597e931b0428bb984dc2bf0736d032a9198c.tar.gz |
Imported Upstream version 217
Diffstat (limited to 'src/resolve')
45 files changed, 12402 insertions, 254 deletions
diff --git a/src/resolve/dns-type.c b/src/resolve/dns-type.c new file mode 100644 index 0000000000..a3e740896f --- /dev/null +++ b/src/resolve/dns-type.c @@ -0,0 +1,45 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + 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 "dns-type.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 unsigned int 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 _DNS_TYPE_INVALID; + + return sc->id; +} diff --git a/src/resolve/dns-type.h b/src/resolve/dns-type.h new file mode 100644 index 0000000000..86951d233a --- /dev/null +++ b/src/resolve/dns-type.h @@ -0,0 +1,120 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + 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/>. +***/ + +#pragma once + +#include "macro.h" + +const char *dns_type_to_string(int type); +int dns_type_from_string(const char *s); + +/* 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_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); diff --git a/src/resolve/dns_type-from-name.gperf b/src/resolve/dns_type-from-name.gperf new file mode 100644 index 0000000000..720dd48966 --- /dev/null +++ b/src/resolve/dns_type-from-name.gperf @@ -0,0 +1,79 @@ +struct dns_type_name { const char* name; int id; }; +%null-strings +%% +A, DNS_TYPE_A +NS, DNS_TYPE_NS +MD, DNS_TYPE_MD +MF, DNS_TYPE_MF +CNAME, DNS_TYPE_CNAME +SOA, DNS_TYPE_SOA +MB, DNS_TYPE_MB +MG, DNS_TYPE_MG +MR, DNS_TYPE_MR +NULL, DNS_TYPE_NULL +WKS, DNS_TYPE_WKS +PTR, DNS_TYPE_PTR +HINFO, DNS_TYPE_HINFO +MINFO, DNS_TYPE_MINFO +MX, DNS_TYPE_MX +TXT, DNS_TYPE_TXT +RP, DNS_TYPE_RP +AFSDB, DNS_TYPE_AFSDB +X25, DNS_TYPE_X25 +ISDN, DNS_TYPE_ISDN +RT, DNS_TYPE_RT +NSAP, DNS_TYPE_NSAP +NSAP_PTR, DNS_TYPE_NSAP_PTR +SIG, DNS_TYPE_SIG +KEY, DNS_TYPE_KEY +PX, DNS_TYPE_PX +GPOS, DNS_TYPE_GPOS +AAAA, DNS_TYPE_AAAA +LOC, DNS_TYPE_LOC +NXT, DNS_TYPE_NXT +EID, DNS_TYPE_EID +NIMLOC, DNS_TYPE_NIMLOC +SRV, DNS_TYPE_SRV +ATMA, DNS_TYPE_ATMA +NAPTR, DNS_TYPE_NAPTR +KX, DNS_TYPE_KX +CERT, DNS_TYPE_CERT +A6, DNS_TYPE_A6 +DNAME, DNS_TYPE_DNAME +SINK, DNS_TYPE_SINK +OPT, DNS_TYPE_OPT +APL, DNS_TYPE_APL +DS, DNS_TYPE_DS +SSHFP, DNS_TYPE_SSHFP +IPSECKEY, DNS_TYPE_IPSECKEY +RRSIG, DNS_TYPE_RRSIG +NSEC, DNS_TYPE_NSEC +DNSKEY, DNS_TYPE_DNSKEY +DHCID, DNS_TYPE_DHCID +NSEC3, DNS_TYPE_NSEC3 +NSEC3PARAM, DNS_TYPE_NSEC3PARAM +TLSA, DNS_TYPE_TLSA +HIP, DNS_TYPE_HIP +NINFO, DNS_TYPE_NINFO +RKEY, DNS_TYPE_RKEY +TALINK, DNS_TYPE_TALINK +CDS, DNS_TYPE_CDS +CDNSKEY, DNS_TYPE_CDNSKEY +SPF, DNS_TYPE_SPF +NID, DNS_TYPE_NID +L32, DNS_TYPE_L32 +L64, DNS_TYPE_L64 +LP, DNS_TYPE_LP +EUI48, DNS_TYPE_EUI48 +EUI64, DNS_TYPE_EUI64 +TKEY, DNS_TYPE_TKEY +TSIG, DNS_TYPE_TSIG +IXFR, DNS_TYPE_IXFR +AXFR, DNS_TYPE_AXFR +MAILB, DNS_TYPE_MAILB +MAILA, DNS_TYPE_MAILA +ANY, DNS_TYPE_ANY +URI, DNS_TYPE_URI +CAA, DNS_TYPE_CAA +TA, DNS_TYPE_TA +DLV, DNS_TYPE_DLV diff --git a/src/resolve/dns_type-from-name.h b/src/resolve/dns_type-from-name.h new file mode 100644 index 0000000000..641f9fec9d --- /dev/null +++ b/src/resolve/dns_type-from-name.h @@ -0,0 +1,283 @@ +/* ANSI-C code produced by gperf version 3.0.4 */ +/* Command-line: gperf -L ANSI-C -t --ignore-case -N lookup_dns_type -H hash_dns_type_name -p -C */ +/* Computed positions: -k'1-2,$' */ + +#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ + && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ + && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \ + && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \ + && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \ + && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \ + && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \ + && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \ + && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \ + && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \ + && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \ + && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \ + && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \ + && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \ + && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \ + && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \ + && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \ + && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \ + && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \ + && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \ + && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \ + && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \ + && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)) +/* The character set is not based on ISO-646. */ +#error "gperf generated tables don't work with this execution character set. Please report a bug to <bug-gnu-gperf@gnu.org>." +#endif + +struct dns_type_name { const char* name; int id; }; + +#define TOTAL_KEYWORDS 76 +#define MIN_WORD_LENGTH 1 +#define MAX_WORD_LENGTH 10 +#define MIN_HASH_VALUE 7 +#define MAX_HASH_VALUE 193 +/* maximum key range = 187, duplicates = 0 */ + +#ifndef GPERF_DOWNCASE +#define GPERF_DOWNCASE 1 +static unsigned char gperf_downcase[256] = + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, + 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, + 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, + 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, + 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, + 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, + 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, + 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, + 255 + }; +#endif + +#ifndef GPERF_CASE_STRCMP +#define GPERF_CASE_STRCMP 1 +static int +gperf_case_strcmp (register const char *s1, register const char *s2) +{ + for (;;) + { + unsigned char c1 = gperf_downcase[(unsigned char)*s1++]; + unsigned char c2 = gperf_downcase[(unsigned char)*s2++]; + if (c1 != 0 && c1 == c2) + continue; + return (int)c1 - (int)c2; + } +} +#endif + +#ifdef __GNUC__ +__inline +#else +#ifdef __cplusplus +inline +#endif +#endif +static unsigned int +hash_dns_type_name (register const char *str, register unsigned int len) +{ + static const unsigned char asso_values[] = + { + 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, + 15, 15, 10, 90, 15, 194, 0, 194, 194, 194, + 194, 194, 194, 194, 194, 5, 25, 25, 30, 25, + 45, 60, 55, 10, 194, 30, 85, 15, 10, 80, + 5, 194, 5, 5, 0, 85, 5, 70, 20, 25, + 194, 194, 194, 194, 194, 194, 194, 5, 25, 25, + 30, 25, 45, 60, 55, 10, 194, 30, 85, 15, + 10, 80, 5, 194, 5, 5, 0, 85, 5, 70, + 20, 25, 194, 194, 194, 194, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194 + }; + register int hval = len; + + switch (hval) + { + default: + hval += asso_values[(unsigned char)str[1]]; + /*FALLTHROUGH*/ + case 1: + hval += asso_values[(unsigned char)str[0]]; + break; + } + return hval + asso_values[(unsigned char)str[len - 1]]; +} + +#ifdef __GNUC__ +__inline +#if defined __GNUC_STDC_INLINE__ || defined __GNUC_GNU_INLINE__ +__attribute__ ((__gnu_inline__)) +#endif +#endif +const struct dns_type_name * +lookup_dns_type (register const char *str, register unsigned int len) +{ + static const struct dns_type_name wordlist[] = + { + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {(char*)0}, {(char*)0}, {(char*)0}, + {"RT", DNS_TYPE_RT}, + {(char*)0}, {(char*)0}, {(char*)0}, + {"A", DNS_TYPE_A}, + {"TA", DNS_TYPE_TA}, + {"PTR", DNS_TYPE_PTR}, + {"ATMA", DNS_TYPE_ATMA}, + {(char*)0}, {(char*)0}, + {"RP", DNS_TYPE_RP}, + {"SRV", DNS_TYPE_SRV}, + {"AAAA", DNS_TYPE_AAAA}, + {"SSHFP", DNS_TYPE_SSHFP}, + {(char*)0}, + {"NS", DNS_TYPE_NS}, + {"TXT", DNS_TYPE_TXT}, + {"NSAP", DNS_TYPE_NSAP}, + {"NAPTR", DNS_TYPE_NAPTR}, + {(char*)0}, + {"MR", DNS_TYPE_MR}, + {"NSAP_PTR", DNS_TYPE_NSAP_PTR}, + {"ISDN", DNS_TYPE_ISDN}, + {"MAILA", DNS_TYPE_MAILA}, + {(char*)0}, {(char*)0}, + {"NXT", DNS_TYPE_NXT}, + {"AXFR", DNS_TYPE_AXFR}, + {"NSEC3", DNS_TYPE_NSEC3}, + {(char*)0}, + {"A6", DNS_TYPE_A6}, + {"CAA", DNS_TYPE_CAA}, + {"IXFR", DNS_TYPE_IXFR}, + {"NSEC3PARAM", DNS_TYPE_NSEC3PARAM}, + {"TALINK", DNS_TYPE_TALINK}, + {"DS", DNS_TYPE_DS}, + {"ANY", DNS_TYPE_ANY}, + {"NSEC", DNS_TYPE_NSEC}, + {(char*)0}, {(char*)0}, + {"PX", DNS_TYPE_PX}, + {"IPSECKEY", DNS_TYPE_IPSECKEY}, + {"SINK", DNS_TYPE_SINK}, + {"MAILB", DNS_TYPE_MAILB}, + {"NIMLOC", DNS_TYPE_NIMLOC}, + {(char*)0}, + {"NID", DNS_TYPE_NID}, + {"CERT", DNS_TYPE_CERT}, + {(char*)0}, {(char*)0}, + {"MX", DNS_TYPE_MX}, + {"SPF", DNS_TYPE_SPF}, + {"TKEY", DNS_TYPE_TKEY}, + {(char*)0}, {(char*)0}, {(char*)0}, + {"CDS", DNS_TYPE_CDS}, + {"RKEY", DNS_TYPE_RKEY}, + {"CNAME", DNS_TYPE_CNAME}, + {(char*)0}, + {"MB", DNS_TYPE_MB}, + {"EID", DNS_TYPE_EID}, + {"TSIG", DNS_TYPE_TSIG}, + {"DNAME", DNS_TYPE_DNAME}, + {"DNSKEY", DNS_TYPE_DNSKEY}, + {"KX", DNS_TYPE_KX}, + {"HIP", DNS_TYPE_HIP}, + {"GPOS", DNS_TYPE_GPOS}, + {"RRSIG", DNS_TYPE_RRSIG}, + {(char*)0}, + {"MD", DNS_TYPE_MD}, + {"SIG", DNS_TYPE_SIG}, + {(char*)0}, + {"AFSDB", DNS_TYPE_AFSDB}, + {(char*)0}, {(char*)0}, + {"KEY", DNS_TYPE_KEY}, + {(char*)0}, {(char*)0}, {(char*)0}, + {"CDNSKEY", DNS_TYPE_CDNSKEY}, + {"OPT", DNS_TYPE_OPT}, + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {"SOA", DNS_TYPE_SOA}, + {"TLSA", DNS_TYPE_TLSA}, + {(char*)0}, {(char*)0}, + {"LP", DNS_TYPE_LP}, + {"APL", DNS_TYPE_APL}, + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {"URI", DNS_TYPE_URI}, + {(char*)0}, + {"NINFO", DNS_TYPE_NINFO}, + {(char*)0}, + {"MF", DNS_TYPE_MF}, + {"WKS", DNS_TYPE_WKS}, + {(char*)0}, + {"MINFO", DNS_TYPE_MINFO}, + {(char*)0}, {(char*)0}, + {"L64", DNS_TYPE_L64}, + {(char*)0}, + {"EUI48", DNS_TYPE_EUI48}, + {(char*)0}, {(char*)0}, + {"L32", DNS_TYPE_L32}, + {(char*)0}, + {"DHCID", DNS_TYPE_DHCID}, + {(char*)0}, {(char*)0}, + {"DLV", DNS_TYPE_DLV}, + {(char*)0}, + {"EUI64", DNS_TYPE_EUI64}, + {(char*)0}, {(char*)0}, + {"X25", DNS_TYPE_X25}, + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {"MG", DNS_TYPE_MG}, + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {"HINFO", DNS_TYPE_HINFO}, + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {(char*)0}, + {"NULL", DNS_TYPE_NULL}, + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {(char*)0}, {(char*)0}, {(char*)0}, {(char*)0}, + {"LOC", DNS_TYPE_LOC} + }; + + if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) + { + register int key = hash_dns_type_name (str, len); + + if (key <= MAX_HASH_VALUE && key >= 0) + { + register const char *s = wordlist[key].name; + + if (s && (((unsigned char)*str ^ (unsigned char)*s) & ~32) == 0 && !gperf_case_strcmp (str, s)) + return &wordlist[key]; + } + } + return 0; +} diff --git a/src/resolve/dns_type-to-name.h b/src/resolve/dns_type-to-name.h new file mode 100644 index 0000000000..da5dd371ea --- /dev/null +++ b/src/resolve/dns_type-to-name.h @@ -0,0 +1,83 @@ +const char *dns_type_to_string(int type) { + switch(type) { + case DNS_TYPE_A: return "A"; + case DNS_TYPE_NS: return "NS"; + case DNS_TYPE_MD: return "MD"; + case DNS_TYPE_MF: return "MF"; + case DNS_TYPE_CNAME: return "CNAME"; + case DNS_TYPE_SOA: return "SOA"; + case DNS_TYPE_MB: return "MB"; + case DNS_TYPE_MG: return "MG"; + case DNS_TYPE_MR: return "MR"; + case DNS_TYPE_NULL: return "NULL"; + case DNS_TYPE_WKS: return "WKS"; + case DNS_TYPE_PTR: return "PTR"; + case DNS_TYPE_HINFO: return "HINFO"; + case DNS_TYPE_MINFO: return "MINFO"; + case DNS_TYPE_MX: return "MX"; + case DNS_TYPE_TXT: return "TXT"; + case DNS_TYPE_RP: return "RP"; + case DNS_TYPE_AFSDB: return "AFSDB"; + case DNS_TYPE_X25: return "X25"; + case DNS_TYPE_ISDN: return "ISDN"; + case DNS_TYPE_RT: return "RT"; + case DNS_TYPE_NSAP: return "NSAP"; + case DNS_TYPE_NSAP_PTR: return "NSAP-PTR"; + case DNS_TYPE_SIG: return "SIG"; + case DNS_TYPE_KEY: return "KEY"; + case DNS_TYPE_PX: return "PX"; + case DNS_TYPE_GPOS: return "GPOS"; + case DNS_TYPE_AAAA: return "AAAA"; + case DNS_TYPE_LOC: return "LOC"; + case DNS_TYPE_NXT: return "NXT"; + case DNS_TYPE_EID: return "EID"; + case DNS_TYPE_NIMLOC: return "NIMLOC"; + case DNS_TYPE_SRV: return "SRV"; + case DNS_TYPE_ATMA: return "ATMA"; + case DNS_TYPE_NAPTR: return "NAPTR"; + case DNS_TYPE_KX: return "KX"; + case DNS_TYPE_CERT: return "CERT"; + case DNS_TYPE_A6: return "A6"; + case DNS_TYPE_DNAME: return "DNAME"; + case DNS_TYPE_SINK: return "SINK"; + case DNS_TYPE_OPT: return "OPT"; + case DNS_TYPE_APL: return "APL"; + case DNS_TYPE_DS: return "DS"; + case DNS_TYPE_SSHFP: return "SSHFP"; + case DNS_TYPE_IPSECKEY: return "IPSECKEY"; + case DNS_TYPE_RRSIG: return "RRSIG"; + case DNS_TYPE_NSEC: return "NSEC"; + case DNS_TYPE_DNSKEY: return "DNSKEY"; + case DNS_TYPE_DHCID: return "DHCID"; + case DNS_TYPE_NSEC3: return "NSEC3"; + case DNS_TYPE_NSEC3PARAM: return "NSEC3PARAM"; + case DNS_TYPE_TLSA: return "TLSA"; + case DNS_TYPE_HIP: return "HIP"; + case DNS_TYPE_NINFO: return "NINFO"; + case DNS_TYPE_RKEY: return "RKEY"; + case DNS_TYPE_TALINK: return "TALINK"; + case DNS_TYPE_CDS: return "CDS"; + case DNS_TYPE_CDNSKEY: return "CDNSKEY"; + case DNS_TYPE_SPF: return "SPF"; + case DNS_TYPE_NID: return "NID"; + case DNS_TYPE_L32: return "L32"; + case DNS_TYPE_L64: return "L64"; + case DNS_TYPE_LP: return "LP"; + case DNS_TYPE_EUI48: return "EUI48"; + case DNS_TYPE_EUI64: return "EUI64"; + case DNS_TYPE_TKEY: return "TKEY"; + case DNS_TYPE_TSIG: return "TSIG"; + case DNS_TYPE_IXFR: return "IXFR"; + case DNS_TYPE_AXFR: return "AXFR"; + case DNS_TYPE_MAILB: return "MAILB"; + case DNS_TYPE_MAILA: return "MAILA"; + case DNS_TYPE_ANY: return "ANY"; + case DNS_TYPE_URI: return "URI"; + case DNS_TYPE_CAA: return "CAA"; + case DNS_TYPE_TA: return "TA"; + case DNS_TYPE_DLV: return "DLV"; + +default: return NULL; + } +} + diff --git a/src/resolve/org.freedesktop.resolve1.conf b/src/resolve/org.freedesktop.resolve1.conf new file mode 100644 index 0000000000..25b09774e5 --- /dev/null +++ b/src/resolve/org.freedesktop.resolve1.conf @@ -0,0 +1,27 @@ +<?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 new file mode 100644 index 0000000000..7ac5c323f0 --- /dev/null +++ b/src/resolve/org.freedesktop.resolve1.service @@ -0,0 +1,12 @@ +# 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/resolved-bus.c b/src/resolve/resolved-bus.c new file mode 100644 index 0000000000..0029023bcc --- /dev/null +++ b/src/resolve/resolved-bus.c @@ -0,0 +1,761 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "bus-errors.h" +#include "bus-util.h" + +#include "resolved-dns-domain.h" +#include "resolved-bus.h" +#include "resolved-def.h" + +static int reply_query_state(DnsQuery *q) { + _cleanup_free_ char *ip = NULL; + const char *name; + int r; + + if (q->request_hostname) + name = q->request_hostname; + else { + r = in_addr_to_string(q->request_family, &q->request_address, &ip); + if (r < 0) + return r; + + name = ip; + } + + 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_RESOURCES: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_RESOURCES, "Not enough resources"); + + case DNS_TRANSACTION_ABORTED: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "Query aborted"); + + case DNS_TRANSACTION_FAILURE: { + _cleanup_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", name); + else { + const char *rc, *n; + char p[3]; /* the rcode is 4 bits long */ + + rc = dns_rcode_to_string(q->answer_rcode); + if (!rc) { + sprintf(p, "%i", q->answer_rcode); + rc = p; + } + + n = strappenda(_BUS_ERROR_DNS, rc); + sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error %s", name, rc); + } + + return sd_bus_reply_method_error(q->request, &error); + } + + case DNS_TRANSACTION_NULL: + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_SUCCESS: + default: + assert_not_reached("Impossible state"); + } +} + +static int append_address(sd_bus_message *reply, DnsResourceRecord *rr) { + int r; + + assert(reply); + assert(rr); + + r = sd_bus_message_open_container(reply, 'r', "iay"); + 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 *cname = NULL, *canonical = NULL; + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + unsigned added = 0, i; + int r; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + r = sd_bus_message_new_method_return(q->request, &reply); + if (r < 0) + goto finish; + + r = sd_bus_message_append(reply, "i", q->answer_ifindex); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "(iay)"); + if (r < 0) + goto finish; + + if (q->answer) { + answer = dns_answer_ref(q->answer); + + for (i = 0; i < answer->n_rrs; i++) { + r = dns_question_matches_rr(q->question, answer->rrs[i]); + if (r < 0) + goto finish; + if (r == 0) { + /* Hmm, if this is not an address record, + maybe it's a cname? If so, remember this */ + r = dns_question_matches_cname(q->question, answer->rrs[i]); + if (r < 0) + goto finish; + if (r > 0) + cname = dns_resource_record_ref(answer->rrs[i]); + + continue; + } + + r = append_address(reply, answer->rrs[i]); + if (r < 0) + goto finish; + + if (!canonical) + canonical = dns_resource_record_ref(answer->rrs[i]); + + added ++; + } + } + + if (added <= 0) { + if (!cname) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of requested type", q->request_hostname); + goto finish; + } + + /* This has a cname? Then update the query with the + * new cname. */ + r = dns_query_cname_redirect(q, cname->cname.name); + if (r < 0) { + if (r == -ELOOP) + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop on '%s'", q->request_hostname); + else + r = sd_bus_reply_method_errno(q->request, -r, NULL); + + goto finish; + } + + /* Before we restart the query, let's see if any of + * the RRs we already got already answers our query */ + for (i = 0; i < answer->n_rrs; i++) { + r = dns_question_matches_rr(q->question, answer->rrs[i]); + if (r < 0) + goto finish; + if (r == 0) + continue; + + r = append_address(reply, answer->rrs[i]); + if (r < 0) + goto finish; + + if (!canonical) + canonical = dns_resource_record_ref(answer->rrs[i]); + + added++; + } + + /* If we didn't find anything, then let's restart the + * query, this time with the cname */ + if (added <= 0) { + r = dns_query_go(q); + if (r == -ESRCH) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_NAME_SERVERS, "No appropriate name servers or networks for name found"); + goto finish; + } + if (r < 0) { + r = sd_bus_reply_method_errno(q->request, -r, NULL); + goto finish; + } + + return; + } + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; + + /* Return the precise spelling and uppercasing reported by the server */ + assert(canonical); + r = sd_bus_message_append(reply, "st", DNS_RESOURCE_KEY_NAME(canonical->key), SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family)); + if (r < 0) + goto finish; + + r = sd_bus_send(q->manager->bus, reply, NULL); + +finish: + if (r < 0) { + log_error("Failed to send hostname reply: %s", strerror(-r)); + sd_bus_reply_method_errno(q->request, -r, NULL); + } + + dns_query_free(q); +} + +static int check_ifindex_flags(int ifindex, uint64_t *flags, 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_FLAGS_ALL) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter"); + + if (*flags == 0) + *flags = SD_RESOLVED_FLAGS_DEFAULT; + + return 0; +} + +static int bus_method_resolve_hostname(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + Manager *m = userdata; + const char *hostname; + int family, ifindex; + uint64_t flags; + DnsQuery *q; + int r; + + assert(bus); + assert(message); + assert(m); + + 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 = dns_name_normalize(hostname, NULL); + if (r < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", hostname); + + r = check_ifindex_flags(ifindex, &flags, error); + if (r < 0) + return r; + + question = dns_question_new(family == AF_UNSPEC ? 2 : 1); + if (!question) + 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, hostname); + if (!key) + return -ENOMEM; + + r = dns_question_add(question, 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, hostname); + if (!key) + return -ENOMEM; + + r = dns_question_add(question, key); + if (r < 0) + return r; + } + + r = dns_query_new(m, &q, question, ifindex, flags); + if (r < 0) + return r; + + q->request = sd_bus_message_ref(message); + q->request_family = family; + q->request_hostname = hostname; + q->complete = bus_method_resolve_hostname_complete; + + r = dns_query_bus_track(q, bus, message); + if (r < 0) + return r; + + r = dns_query_go(q); + if (r < 0) { + dns_query_free(q); + + if (r == -ESRCH) + sd_bus_error_setf(error, BUS_ERROR_NO_NAME_SERVERS, "No appropriate name servers or networks for name found"); + + return r; + } + + return 1; +} + +static void bus_method_resolve_address_complete(DnsQuery *q) { + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + unsigned added = 0, i; + int r; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + r = sd_bus_message_new_method_return(q->request, &reply); + if (r < 0) + goto finish; + + r = sd_bus_message_append(reply, "i", q->answer_ifindex); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "s"); + if (r < 0) + goto finish; + + if (q->answer) { + answer = dns_answer_ref(q->answer); + + for (i = 0; i < answer->n_rrs; i++) { + r = dns_question_matches_rr(q->question, answer->rrs[i]); + if (r < 0) + goto finish; + if (r == 0) + continue; + + r = sd_bus_message_append(reply, "s", answer->rrs[i]->ptr.name); + if (r < 0) + goto finish; + + added ++; + } + } + + if (added <= 0) { + _cleanup_free_ char *ip = NULL; + + 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", 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)); + if (r < 0) + goto finish; + + r = sd_bus_send(q->manager->bus, reply, NULL); + +finish: + if (r < 0) { + log_error("Failed to send address reply: %s", strerror(-r)); + sd_bus_reply_method_errno(q->request, -r, NULL); + } + + dns_query_free(q); +} + +static int bus_method_resolve_address(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_free_ char *reverse = NULL; + Manager *m = userdata; + int family, ifindex; + uint64_t flags; + const void *d; + DnsQuery *q; + size_t sz; + int r; + + assert(bus); + assert(message); + assert(m); + + 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, error); + if (r < 0) + return r; + + r = dns_name_reverse(family, d, &reverse); + if (r < 0) + return r; + + question = dns_question_new(1); + if (!question) + 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(question, key); + if (r < 0) + return r; + + r = dns_query_new(m, &q, question, ifindex, flags); + 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, bus, message); + if (r < 0) + return r; + + r = dns_query_go(q); + if (r < 0) { + dns_query_free(q); + + if (r == -ESRCH) + sd_bus_error_setf(error, BUS_ERROR_NO_NAME_SERVERS, "No appropriate name servers or networks for name found"); + + return r; + } + + return 1; +} + +static void bus_method_resolve_record_complete(DnsQuery *q) { + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + unsigned added = 0, i; + int r; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + r = sd_bus_message_new_method_return(q->request, &reply); + if (r < 0) + goto finish; + + r = sd_bus_message_append(reply, "i", q->answer_ifindex); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "(qqay)"); + if (r < 0) + goto finish; + + if (q->answer) { + answer = dns_answer_ref(q->answer); + + for (i = 0; i < answer->n_rrs; i++) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + size_t start; + + r = dns_question_matches_rr(q->question, answer->rrs[i]); + if (r < 0) + goto finish; + if (r == 0) + continue; + + r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0); + if (r < 0) + goto finish; + + r = dns_packet_append_rr(p, answer->rrs[i], &start); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'r', "qqay"); + if (r < 0) + goto finish; + + r = sd_bus_message_append(reply, "qq", answer->rrs[i]->key->class, answer->rrs[i]->key->type); + if (r < 0) + goto finish; + + r = sd_bus_message_append_array(reply, 'y', DNS_PACKET_DATA(p) + start, p->size - start); + if (r < 0) + goto finish; + + r = sd_bus_message_close_container(reply); + 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", q->request_hostname); + 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)); + if (r < 0) + goto finish; + + r = sd_bus_send(q->manager->bus, reply, NULL); + +finish: + if (r < 0) { + log_error("Failed to send record reply: %s", strerror(-r)); + sd_bus_reply_method_errno(q->request, -r, NULL); + } + + dns_query_free(q); +} + +static int bus_method_resolve_record(sd_bus *bus, 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(bus); + assert(message); + assert(m); + + r = sd_bus_message_read(message, "isqqt", &ifindex, &name, &class, &type, &flags); + if (r < 0) + return r; + + r = dns_name_normalize(name, NULL); + if (r < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid name '%s'", name); + + r = check_ifindex_flags(ifindex, &flags, 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, ifindex, flags); + if (r < 0) + return r; + + q->request = sd_bus_message_ref(message); + q->request_hostname = name; + q->complete = bus_method_resolve_record_complete; + + r = dns_query_bus_track(q, bus, message); + if (r < 0) + return r; + + r = dns_query_go(q); + if (r < 0) { + dns_query_free(q); + + if (r == -ESRCH) + sd_bus_error_setf(error, BUS_ERROR_NO_NAME_SERVERS, "No appropriate name servers or networks for name found"); + + return r; + } + + return 1; +} + +static const sd_bus_vtable resolve_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("ResolveHostname", "isit", "ia(iay)st", bus_method_resolve_hostname, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ResolveAddress", "iiayt", "iast", bus_method_resolve_address, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ResolveRecord", "isqqt", "ia(qqay)t", bus_method_resolve_record, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END, +}; + +static int on_bus_retry(sd_event_source *s, usec_t usec, void *userdata) { + Manager *m = userdata; + + assert(s); + assert(m); + + m->bus_retry_event_source = sd_event_source_unref(m->bus_retry_event_source); + + manager_connect_bus(m); + return 0; +} + +static int match_prepare_for_sleep(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *ret_error) { + Manager *m = userdata; + int b, r; + + assert(bus); + assert(bus); + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) { + log_debug("Failed to parse PrepareForSleep signal: %s", strerror(-r)); + 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("Failed to connect to bus, trying again in 5s: %s", strerror(-r)); + + r = sd_event_add_time(m->event, &m->bus_retry_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 5*USEC_PER_SEC, 0, on_bus_retry, m); + if (r < 0) { + log_error("Failed to install bus reconnect time event: %s", strerror(-r)); + return r; + } + + return 0; + } + + r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/resolve1", "org.freedesktop.resolve1.Manager", resolve_vtable, m); + if (r < 0) { + log_error("Failed to register object: %s", strerror(-r)); + return r; + } + + r = sd_bus_request_name(m->bus, "org.freedesktop.resolve1", 0); + if (r < 0) { + log_error("Failed to register name: %s", strerror(-r)); + return r; + } + + r = sd_bus_attach_event(m->bus, m->event, 0); + if (r < 0) { + log_error("Failed to attach bus to event loop: %s", strerror(-r)); + return r; + } + + 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("Failed to add match for PrepareForSleep: %s", strerror(-r)); + + return 0; +} diff --git a/src/resolve/resolved-bus.h b/src/resolve/resolved-bus.h new file mode 100644 index 0000000000..1e72891178 --- /dev/null +++ b/src/resolve/resolved-bus.h @@ -0,0 +1,26 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "resolved-manager.h" + +int manager_connect_bus(Manager *m); diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c new file mode 100644 index 0000000000..63e87f8df5 --- /dev/null +++ b/src/resolve/resolved-conf.c @@ -0,0 +1,154 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + 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 "conf-parser.h" + +#include "resolved-conf.h" + +int manager_parse_dns_server(Manager *m, DnsServerType type, const char *string) { + const char *word, *state; + size_t length; + DnsServer *first; + int r; + + assert(m); + assert(string); + + first = type == DNS_SERVER_FALLBACK ? m->fallback_dns_servers : m->dns_servers; + + FOREACH_WORD_QUOTED(word, length, string, state) { + char buffer[length+1]; + int family; + union in_addr_union addr; + bool found = false; + DnsServer *s; + + memcpy(buffer, word, length); + buffer[length] = 0; + + r = in_addr_from_string_auto(buffer, &family, &addr); + if (r < 0) { + log_warning("Ignoring invalid DNS address '%s'", buffer); + continue; + } + + /* Filter out duplicates */ + LIST_FOREACH(servers, s, first) + if (s->family == family && in_addr_equal(family, &s->address, &addr)) { + found = true; + break; + } + + if (found) + continue; + + r = dns_server_new(m, NULL, type, NULL, family, &addr); + if (r < 0) + return r; + } + + return 0; +} + +int config_parse_dnsv( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Manager *m = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(m); + + if (isempty(rvalue)) + /* Empty assignment means clear the list */ + manager_flush_dns_servers(m, ltype); + else { + /* Otherwise add to the list */ + r = manager_parse_dns_server(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; + + return 0; +} + +int config_parse_support( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Manager *m = userdata; + Support support, *v = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(m); + + support = support_from_string(rvalue); + if (support < 0) { + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, -r, "Failed to parse support level '%s'. Ignoring.", rvalue); + return 0; + } + + support = r ? SUPPORT_YES : SUPPORT_NO; + } + + *v = support; + return 0; +} + +int manager_parse_config_file(Manager *m) { + assert(m); + + return config_parse(NULL, "/etc/systemd/resolved.conf", NULL, + "Resolve\0", + config_item_perf_lookup, resolved_gperf_lookup, + false, false, true, m); +} diff --git a/src/resolve/resolved-conf.h b/src/resolve/resolved-conf.h new file mode 100644 index 0000000000..b3dbea7b6b --- /dev/null +++ b/src/resolve/resolved-conf.h @@ -0,0 +1,32 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#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" + +int manager_parse_dns_server(Manager *m, DnsServerType type, const char *string); +int manager_parse_config_file(Manager *m); + +const struct ConfigPerfItem* resolved_gperf_lookup(const char *key, unsigned length); + +int config_parse_dnsv(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_support(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); diff --git a/src/resolve/resolved-def.h b/src/resolve/resolved-def.h new file mode 100644 index 0000000000..086d111205 --- /dev/null +++ b/src/resolve/resolved-def.h @@ -0,0 +1,30 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#define SD_RESOLVED_DNS ((uint64_t) 1) +#define SD_RESOLVED_LLMNR_IPV4 ((uint64_t) 2) +#define SD_RESOLVED_LLMNR_IPV6 ((uint64_t) 4) +#define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6) + +#define SD_RESOLVED_FLAGS_ALL (SD_RESOLVED_DNS|SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6) +#define SD_RESOLVED_FLAGS_DEFAULT SD_RESOLVED_FLAGS_ALL diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c new file mode 100644 index 0000000000..7c4ab18b58 --- /dev/null +++ b/src/resolve/resolved-dns-answer.c @@ -0,0 +1,238 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "resolved-dns-answer.h" +#include "resolved-dns-domain.h" + +DnsAnswer *dns_answer_new(unsigned n) { + DnsAnswer *a; + + assert(n > 0); + + a = malloc0(offsetof(DnsAnswer, rrs) + sizeof(DnsResourceRecord*) * 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; +} + +DnsAnswer *dns_answer_unref(DnsAnswer *a) { + if (!a) + return NULL; + + assert(a->n_ref > 0); + + if (a->n_ref == 1) { + unsigned i; + + for (i = 0; i < a->n_rrs; i++) + dns_resource_record_unref(a->rrs[i]); + + free(a); + } else + a->n_ref--; + + return NULL; +} + +int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr) { + unsigned i; + int r; + + assert(a); + assert(rr); + + for (i = 0; i < a->n_rrs; i++) { + r = dns_resource_record_equal(a->rrs[i], rr); + if (r < 0) + return r; + if (r > 0) { + /* Entry already exists, keep the entry with + * the higher RR, or the one with TTL 0 */ + + if (rr->ttl == 0 || (rr->ttl > a->rrs[i]->ttl && a->rrs[i]->ttl != 0)) { + dns_resource_record_ref(rr); + dns_resource_record_unref(a->rrs[i]); + a->rrs[i] = rr; + } + + return 0; + } + } + + if (a->n_rrs >= a->n_allocated) + return -ENOSPC; + + a->rrs[a->n_rrs++] = dns_resource_record_ref(rr); + return 1; +} + +int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl) { + _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); +} + +int dns_answer_contains(DnsAnswer *a, DnsResourceKey *key) { + unsigned i; + int r; + + assert(a); + assert(key); + + for (i = 0; i < a->n_rrs; i++) { + r = dns_resource_key_match_rr(key, a->rrs[i]); + if (r < 0) + return r; + if (r > 0) + return 1; + } + + return 0; +} + +int dns_answer_find_soa(DnsAnswer *a, DnsResourceKey *key, DnsResourceRecord **ret) { + unsigned i; + + assert(a); + assert(key); + assert(ret); + + /* For a SOA record we can never find a matching SOA record */ + if (key->type == DNS_TYPE_SOA) + return 0; + + for (i = 0; i < a->n_rrs; i++) { + + if (a->rrs[i]->key->class != DNS_CLASS_IN) + continue; + + if (a->rrs[i]->key->type != DNS_TYPE_SOA) + continue; + + if (dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(a->rrs[i]->key))) { + *ret = a->rrs[i]; + return 1; + } + } + + return 0; +} + +DnsAnswer *dns_answer_merge(DnsAnswer *a, DnsAnswer *b) { + _cleanup_(dns_answer_unrefp) DnsAnswer *ret = NULL; + DnsAnswer *k; + unsigned i; + int r; + + if (a && (!b || b->n_rrs <= 0)) + return dns_answer_ref(a); + if ((!a || a->n_rrs <= 0) && b) + return dns_answer_ref(b); + + ret = dns_answer_new((a ? a->n_rrs : 0) + (b ? b->n_rrs : 0)); + if (!ret) + return NULL; + + if (a) { + for (i = 0; i < a->n_rrs; i++) { + r = dns_answer_add(ret, a->rrs[i]); + if (r < 0) + return NULL; + } + } + + if (b) { + for (i = 0; i < b->n_rrs; i++) { + r = dns_answer_add(ret, b->rrs[i]); + if (r < 0) + return NULL; + } + } + + k = ret; + ret = NULL; + + return k; +} + +void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local) { + DnsResourceRecord **rrs; + unsigned i, start, end; + assert(a); + + 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. */ + + rrs = newa(DnsResourceRecord*, a->n_rrs); + for (i = 0; i < a->n_rrs; i++) { + + if (a->rrs[i]->key->class == DNS_CLASS_IN && + ((a->rrs[i]->key->type == DNS_TYPE_A && in_addr_is_link_local(AF_INET, (union in_addr_union*) &a->rrs[i]->a.in_addr) != prefer_link_local) || + (a->rrs[i]->key->type == DNS_TYPE_AAAA && in_addr_is_link_local(AF_INET6, (union in_addr_union*) &a->rrs[i]->aaaa.in6_addr) != prefer_link_local))) + /* Order address records that are are not preferred to the end of the array */ + rrs[end--] = a->rrs[i]; + else + /* Order all other records to the beginning of the array */ + rrs[start++] = a->rrs[i]; + } + + assert(start == end+1); + memcpy(a->rrs, rrs, sizeof(DnsResourceRecord*) * a->n_rrs); +} diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h new file mode 100644 index 0000000000..af3e462ed5 --- /dev/null +++ b/src/resolve/resolved-dns-answer.h @@ -0,0 +1,48 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +typedef struct DnsAnswer DnsAnswer; + +#include "resolved-dns-rr.h" + +/* A simple array of resource records */ + +struct DnsAnswer { + unsigned n_ref; + unsigned n_rrs, n_allocated; + DnsResourceRecord* rrs[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 dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl); +int dns_answer_contains(DnsAnswer *a, DnsResourceKey *key); +int dns_answer_find_soa(DnsAnswer *a, DnsResourceKey *key, DnsResourceRecord **ret); + +DnsAnswer *dns_answer_merge(DnsAnswer *a, DnsAnswer *b); +void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local); + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref); diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c new file mode 100644 index 0000000000..33ca4d1a45 --- /dev/null +++ b/src/resolve/resolved-dns-cache.c @@ -0,0 +1,564 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "resolved-dns-cache.h" +#include "resolved-dns-packet.h" + +/* Never cache more than 1K entries */ +#define CACHE_MAX 1024 + +/* We never keep any item longer than 10min in our cache */ +#define CACHE_TTL_MAX_USEC (10 * USEC_PER_MINUTE) + +typedef enum DnsCacheItemType DnsCacheItemType; +typedef struct DnsCacheItem DnsCacheItem; + +enum DnsCacheItemType { + DNS_CACHE_POSITIVE, + DNS_CACHE_NODATA, + DNS_CACHE_NXDOMAIN, +}; + +struct DnsCacheItem { + DnsResourceKey *key; + DnsResourceRecord *rr; + usec_t until; + DnsCacheItemType type; + unsigned prioq_idx; + int owner_family; + union in_addr_union owner_address; + 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_remove_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); +} + +void dns_cache_flush(DnsCache *c) { + DnsCacheItem *i; + + assert(c); + + while ((i = hashmap_first(c->by_key))) + dns_cache_item_remove_and_free(c, i); + + assert(hashmap_size(c->by_key) == 0); + assert(prioq_size(c->by_expiry) == 0); + + hashmap_free(c->by_key); + c->by_key = NULL; + + prioq_free(c->by_expiry); + c->by_expiry = NULL; +} + +static void dns_cache_remove(DnsCache *c, DnsResourceKey *key) { + DnsCacheItem *i; + + assert(c); + assert(key); + + while ((i = hashmap_get(c->by_key, key))) + dns_cache_item_remove_and_free(c, i); +} + +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(c, key); + } +} + +void dns_cache_prune(DnsCache *c) { + usec_t t = 0; + + assert(c); + + /* Remove all entries that are past their TTL */ + + for (;;) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + DnsCacheItem *i; + + i = prioq_peek(c->by_expiry); + if (!i) + break; + + if (t <= 0) + t = now(CLOCK_BOOTTIME); + + if (i->until > t) + break; + + /* Take an extra reference to the key so that it + * doesn't go away in the middle of the remove call */ + key = dns_resource_key_ref(i->key); + dns_cache_remove(c, key); + } +} + +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) { + 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 void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsResourceRecord *rr, usec_t timestamp) { + assert(c); + assert(i); + assert(rr); + + 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 = timestamp + MIN(rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC); + + prioq_reshuffle(c->by_expiry, i, &i->prioq_idx); +} + +static int dns_cache_put_positive( + DnsCache *c, + DnsResourceRecord *rr, + usec_t timestamp, + int owner_family, + const union in_addr_union *owner_address) { + + _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL; + DnsCacheItem *existing; + int r; + + assert(c); + assert(rr); + assert(owner_address); + + /* New TTL is 0? Delete the entry... */ + if (rr->ttl <= 0) { + dns_cache_remove(c, rr->key); + return 0; + } + + if (rr->key->class == DNS_CLASS_ANY) + return 0; + if (rr->key->type == DNS_TYPE_ANY) + return 0; + + /* Entry exists already? Update TTL and timestamp */ + existing = dns_cache_get(c, rr); + if (existing) { + dns_cache_item_update_positive(c, existing, rr, timestamp); + 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 = timestamp + MIN(i->rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC); + i->prioq_idx = PRIOQ_IDX_NULL; + i->owner_family = owner_family; + i->owner_address = *owner_address; + + r = dns_cache_link_item(c, i); + if (r < 0) + return r; + + i = NULL; + return 0; +} + +static int dns_cache_put_negative( + DnsCache *c, + DnsResourceKey *key, + int rcode, + usec_t timestamp, + uint32_t soa_ttl, + int owner_family, + const union in_addr_union *owner_address) { + + _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL; + int r; + + assert(c); + assert(key); + assert(owner_address); + + dns_cache_remove(c, key); + + if (key->class == DNS_CLASS_ANY) + return 0; + if (key->type == DNS_TYPE_ANY) + return 0; + if (soa_ttl <= 0) + 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->key = dns_resource_key_ref(key); + i->until = timestamp + MIN(soa_ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC); + i->prioq_idx = PRIOQ_IDX_NULL; + i->owner_family = owner_family; + i->owner_address = *owner_address; + + r = dns_cache_link_item(c, i); + if (r < 0) + return r; + + i = NULL; + return 0; +} + +int dns_cache_put( + DnsCache *c, + DnsQuestion *q, + int rcode, + DnsAnswer *answer, + unsigned max_rrs, + usec_t timestamp, + int owner_family, + const union in_addr_union *owner_address) { + + unsigned i; + int r; + + assert(c); + assert(q); + + /* First, delete all matching old RRs, so that we only keep + * complete by_key in place. */ + for (i = 0; i < q->n_keys; i++) + dns_cache_remove(c, q->keys[i]); + + if (!answer) + return 0; + + for (i = 0; i < answer->n_rrs; i++) + dns_cache_remove(c, answer->rrs[i]->key); + + /* 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; + + /* Make some space for our new entries */ + dns_cache_make_space(c, answer->n_rrs + q->n_keys); + + if (timestamp <= 0) + timestamp = now(CLOCK_BOOTTIME); + + /* Second, add in positive entries for all contained RRs */ + for (i = 0; i < MIN(max_rrs, answer->n_rrs); i++) { + r = dns_cache_put_positive(c, answer->rrs[i], timestamp, owner_family, owner_address); + if (r < 0) + goto fail; + } + + /* Third, add in negative entries for all keys with no RR */ + for (i = 0; i < q->n_keys; i++) { + DnsResourceRecord *soa = NULL; + + r = dns_answer_contains(answer, q->keys[i]); + if (r < 0) + goto fail; + if (r > 0) + continue; + + r = dns_answer_find_soa(answer, q->keys[i], &soa); + if (r < 0) + goto fail; + if (r == 0) + continue; + + r = dns_cache_put_negative(c, q->keys[i], rcode, timestamp, MIN(soa->soa.minimum, soa->ttl), 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 */ + + for (i = 0; i < q->n_keys; i++) + dns_cache_remove(c, q->keys[i]); + for (i = 0; i < answer->n_rrs; i++) + dns_cache_remove(c, answer->rrs[i]->key); + + return r; +} + +int dns_cache_lookup(DnsCache *c, DnsQuestion *q, int *rcode, DnsAnswer **ret) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + unsigned i, n = 0; + int r; + bool nxdomain = false; + + assert(c); + assert(q); + assert(rcode); + assert(ret); + + if (q->n_keys <= 0) { + *ret = NULL; + *rcode = 0; + return 0; + } + + for (i = 0; i < q->n_keys; i++) { + DnsCacheItem *j; + + if (q->keys[i]->type == DNS_TYPE_ANY || + q->keys[i]->class == DNS_CLASS_ANY) { + /* If we have ANY lookups we simply refresh */ + *ret = NULL; + *rcode = 0; + return 0; + } + + j = hashmap_get(c->by_key, q->keys[i]); + if (!j) { + /* If one question cannot be answered we need to refresh */ + *ret = NULL; + *rcode = 0; + return 0; + } + + LIST_FOREACH(by_key, j, j) { + if (j->rr) + n++; + else if (j->type == DNS_CACHE_NXDOMAIN) + nxdomain = true; + } + } + + if (n <= 0) { + *ret = NULL; + *rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS; + return 1; + } + + answer = dns_answer_new(n); + if (!answer) + return -ENOMEM; + + for (i = 0; i < q->n_keys; i++) { + DnsCacheItem *j; + + j = hashmap_get(c->by_key, q->keys[i]); + LIST_FOREACH(by_key, j, j) { + if (j->rr) { + r = dns_answer_add(answer, j->rr); + if (r < 0) + return r; + } + } + } + + *ret = answer; + *rcode = DNS_RCODE_SUCCESS; + 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; +} diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h new file mode 100644 index 0000000000..e92280c319 --- /dev/null +++ b/src/resolve/resolved-dns-cache.h @@ -0,0 +1,46 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +#include "hashmap.h" +#include "prioq.h" +#include "time-util.h" +#include "list.h" + +typedef struct DnsCache { + Hashmap *by_key; + Prioq *by_expiry; +} DnsCache; + +#include "resolved-dns-rr.h" +#include "resolved-dns-question.h" +#include "resolved-dns-answer.h" + +void dns_cache_flush(DnsCache *c); +void dns_cache_prune(DnsCache *c); + +int dns_cache_put(DnsCache *c, DnsQuestion *q, int rcode, DnsAnswer *answer, unsigned max_rrs, usec_t timestamp, int owner_family, const union in_addr_union *owner_address); +int dns_cache_lookup(DnsCache *c, DnsQuestion *q, int *rcode, DnsAnswer **answer); + +int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address); diff --git a/src/resolve/resolved-dns-domain.c b/src/resolve/resolved-dns-domain.c new file mode 100644 index 0000000000..e1eb3ddfe5 --- /dev/null +++ b/src/resolve/resolved-dns-domain.c @@ -0,0 +1,613 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. + ***/ + +#ifdef HAVE_LIBIDN +#include <idna.h> +#include <stringprep.h> +#endif + +#include "resolved-dns-domain.h" + +int dns_label_unescape(const char **name, char *dest, size_t sz) { + const char *n; + char *d; + int r = 0; + + assert(name); + assert(*name); + assert(dest); + + n = *name; + d = dest; + + for (;;) { + if (*n == '.') { + n++; + break; + } + + if (*n == 0) + break; + + if (sz <= 0) + return -ENOSPC; + + if (r >= DNS_LABEL_MAX) + return -EINVAL; + + if (*n == '\\') { + /* Escaped character */ + + n++; + + if (*n == 0) + /* Ending NUL */ + return -EINVAL; + + else if (*n == '\\' || *n == '.') { + /* Escaped backslash or dot */ + *(d++) = *(n++); + sz--; + r++; + + } else if (n[0] >= '0' && n[0] <= '9') { + unsigned k; + + /* Escaped literal ASCII character */ + + if (!(n[1] >= '0' && n[1] <= '9') || + !(n[2] >= '0' && n[2] <= '9')) + return -EINVAL; + + k = ((unsigned) (n[0] - '0') * 100) + + ((unsigned) (n[1] - '0') * 10) + + ((unsigned) (n[2] - '0')); + + /* Don't allow CC characters or anything that doesn't fit in 8bit */ + if (k < ' ' || k > 255 || k == 127) + return -EINVAL; + + *(d++) = (char) k; + sz--; + r++; + + n += 3; + } else + return -EINVAL; + + } else if ((uint8_t) *n >= (uint8_t) ' ' && *n != 127) { + + /* Normal character */ + *(d++) = *(n++); + sz--; + r++; + } else + return -EINVAL; + } + + /* Empty label that is not at the end? */ + if (r == 0 && *n) + return -EINVAL; + + if (sz >= 1) + *d = 0; + + *name = n; + return r; +} + +int dns_label_escape(const char *p, size_t l, char **ret) { + _cleanup_free_ char *s = NULL; + char *q; + int r; + + assert(p); + assert(ret); + + if (l > DNS_LABEL_MAX) + return -EINVAL; + + s = malloc(l * 4 + 1); + if (!s) + return -ENOMEM; + + q = s; + while (l > 0) { + + if (*p == '.' || *p == '\\') { + + /* Dot or backslash */ + *(q++) = '\\'; + *(q++) = *p; + + } else if (*p == '_' || + *p == '-' || + (*p >= '0' && *p <= '9') || + (*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z')) { + + /* Proper character */ + *(q++) = *p; + } else if ((uint8_t) *p >= (uint8_t) ' ' && *p != 127) { + + /* Everything else */ + *(q++) = '\\'; + *(q++) = '0' + (char) ((uint8_t) *p / 100); + *(q++) = '0' + (char) (((uint8_t) *p / 10) % 10); + *(q++) = '0' + (char) ((uint8_t) *p % 10); + + } else + return -EINVAL; + + p++; + l--; + } + + *q = 0; + *ret = s; + r = q - s; + s = NULL; + + return r; +} + +int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) { +#ifdef HAVE_LIBIDN + _cleanup_free_ uint32_t *input = NULL; + size_t input_size; + const char *p; + bool contains_8bit = false; + + assert(encoded); + assert(decoded); + assert(decoded_max >= DNS_LABEL_MAX); + + if (encoded_size <= 0) + return 0; + + for (p = encoded; p < encoded + encoded_size; p++) + if ((uint8_t) *p > 127) + contains_8bit = true; + + if (!contains_8bit) + return 0; + + input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size); + if (!input) + return -ENOMEM; + + if (idna_to_ascii_4i(input, input_size, decoded, 0) != 0) + return -EINVAL; + + return strlen(decoded); +#else + return 0; +#endif +} + +int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) { +#ifdef HAVE_LIBIDN + size_t input_size, output_size; + _cleanup_free_ uint32_t *input = NULL; + _cleanup_free_ char *result = NULL; + uint32_t *output = NULL; + size_t w; + + /* To be invoked after unescaping */ + + assert(encoded); + assert(decoded); + + if (encoded_size < sizeof(IDNA_ACE_PREFIX)-1) + return 0; + + if (memcmp(encoded, IDNA_ACE_PREFIX, sizeof(IDNA_ACE_PREFIX) -1) != 0) + return 0; + + input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size); + if (!input) + return -ENOMEM; + + output_size = input_size; + output = newa(uint32_t, output_size); + + idna_to_unicode_44i(input, input_size, output, &output_size, 0); + + result = stringprep_ucs4_to_utf8(output, output_size, NULL, &w); + if (!result) + return -ENOMEM; + if (w <= 0) + return 0; + if (w+1 > decoded_max) + return -EINVAL; + + memcpy(decoded, result, w+1); + return w; +#else + return 0; +#endif +} + +int dns_name_normalize(const char *s, char **_ret) { + _cleanup_free_ char *ret = NULL; + size_t n = 0, allocated = 0; + const char *p = s; + bool first = true; + int r; + + assert(s); + + for (;;) { + _cleanup_free_ char *t = NULL; + char label[DNS_LABEL_MAX]; + int k; + + r = dns_label_unescape(&p, label, sizeof(label)); + if (r < 0) + return r; + if (r == 0) { + if (*p != 0) + return -EINVAL; + break; + } + + k = dns_label_undo_idna(label, r, label, sizeof(label)); + if (k < 0) + return k; + if (k > 0) + r = k; + + r = dns_label_escape(label, r, &t); + if (r < 0) + return r; + + if (!GREEDY_REALLOC(ret, allocated, n + !first + strlen(t) + 1)) + return -ENOMEM; + + if (!first) + ret[n++] = '.'; + else + first = false; + + memcpy(ret + n, t, r); + n += r; + } + + if (n > DNS_NAME_MAX) + return -EINVAL; + + if (!GREEDY_REALLOC(ret, allocated, n + 1)) + return -ENOMEM; + + ret[n] = 0; + + if (_ret) { + *_ret = ret; + ret = NULL; + } + + return 0; +} + +unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_SIZE]) { + const char *p = s; + unsigned long ul = hash_key[0]; + int r; + + assert(p); + + while (*p) { + char label[DNS_LABEL_MAX+1]; + int k; + + r = dns_label_unescape(&p, label, sizeof(label)); + if (r < 0) + break; + + k = dns_label_undo_idna(label, r, label, sizeof(label)); + if (k < 0) + break; + if (k > 0) + r = k; + + label[r] = 0; + ascii_strlower(label); + + ul = ul * hash_key[1] + ul + string_hash_func(label, hash_key); + } + + return ul; +} + +int dns_name_compare_func(const void *a, const void *b) { + const char *x = a, *y = b; + int r, q, k, w; + + assert(a); + assert(b); + + for (;;) { + char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; + + if (*x == 0 && *y == 0) + return 0; + + r = dns_label_unescape(&x, la, sizeof(la)); + q = dns_label_unescape(&y, lb, sizeof(lb)); + if (r < 0 || q < 0) + return r - q; + + k = dns_label_undo_idna(la, r, la, sizeof(la)); + w = dns_label_undo_idna(lb, q, lb, sizeof(lb)); + if (k < 0 || w < 0) + return k - w; + if (k > 0) + r = k; + if (w > 0) + r = w; + + la[r] = lb[q] = 0; + r = strcasecmp(la, lb); + if (r != 0) + return r; + } +} + +const struct hash_ops dns_name_hash_ops = { + .hash = dns_name_hash_func, + .compare = dns_name_compare_func +}; + +int dns_name_equal(const char *x, const char *y) { + int r, q, k, w; + + assert(x); + assert(y); + + for (;;) { + char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; + + if (*x == 0 && *y == 0) + return true; + + r = dns_label_unescape(&x, la, sizeof(la)); + if (r < 0) + return r; + + k = dns_label_undo_idna(la, r, la, sizeof(la)); + if (k < 0) + return k; + if (k > 0) + r = k; + + q = dns_label_unescape(&y, lb, sizeof(lb)); + if (q < 0) + return q; + w = dns_label_undo_idna(lb, q, lb, sizeof(lb)); + if (w < 0) + return w; + if (w > 0) + q = w; + + la[r] = lb[q] = 0; + if (strcasecmp(la, lb)) + return false; + } +} + +int dns_name_endswith(const char *name, const char *suffix) { + const char *n, *s, *saved_n = NULL; + int r, q, k, w; + + assert(name); + assert(suffix); + + n = name; + s = suffix; + + for (;;) { + char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1]; + + r = dns_label_unescape(&n, ln, sizeof(ln)); + if (r < 0) + return r; + k = dns_label_undo_idna(ln, r, ln, sizeof(ln)); + if (k < 0) + return k; + if (k > 0) + r = k; + + if (!saved_n) + saved_n = n; + + q = dns_label_unescape(&s, ls, sizeof(ls)); + if (q < 0) + return q; + w = dns_label_undo_idna(ls, q, ls, sizeof(ls)); + if (w < 0) + return w; + if (w > 0) + q = w; + + if (r == 0 && q == 0) + return true; + if (r == 0 && saved_n == n) + return false; + + ln[r] = ls[q] = 0; + + if (r != q || strcasecmp(ln, ls)) { + + /* Not the same, let's jump back, and try with the next label again */ + s = suffix; + n = saved_n; + saved_n = NULL; + } + } +} + +int dns_name_reverse(int family, const union in_addr_union *a, char **ret) { + const uint8_t *p; + int r; + + assert(a); + assert(ret); + + p = (const uint8_t*) a; + + if (family == AF_INET) + r = asprintf(ret, "%u.%u.%u.%u.in-addr.arpa", p[3], p[2], p[1], p[0]); + else if (family == AF_INET6) + r = asprintf(ret, "%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.ip6.arpa", + hexchar(p[15] & 0xF), hexchar(p[15] >> 4), hexchar(p[14] & 0xF), hexchar(p[14] >> 4), + hexchar(p[13] & 0xF), hexchar(p[13] >> 4), hexchar(p[12] & 0xF), hexchar(p[12] >> 4), + hexchar(p[11] & 0xF), hexchar(p[11] >> 4), hexchar(p[10] & 0xF), hexchar(p[10] >> 4), + hexchar(p[ 9] & 0xF), hexchar(p[ 9] >> 4), hexchar(p[ 8] & 0xF), hexchar(p[ 8] >> 4), + hexchar(p[ 7] & 0xF), hexchar(p[ 7] >> 4), hexchar(p[ 6] & 0xF), hexchar(p[ 6] >> 4), + hexchar(p[ 5] & 0xF), hexchar(p[ 5] >> 4), hexchar(p[ 4] & 0xF), hexchar(p[ 4] >> 4), + hexchar(p[ 3] & 0xF), hexchar(p[ 3] >> 4), hexchar(p[ 2] & 0xF), hexchar(p[ 2] >> 4), + hexchar(p[ 1] & 0xF), hexchar(p[ 1] >> 4), hexchar(p[ 0] & 0xF), hexchar(p[ 0] >> 4)); + else + return -EAFNOSUPPORT; + if (r < 0) + return -ENOMEM; + + return 0; +} + +int dns_name_address(const char *p, int *family, union in_addr_union *address) { + int r; + + assert(p); + assert(family); + assert(address); + + r = dns_name_endswith(p, "in-addr.arpa"); + if (r < 0) + return r; + if (r > 0) { + uint8_t a[4]; + unsigned i; + + for (i = 0; i < ELEMENTSOF(a); i++) { + char label[DNS_LABEL_MAX+1]; + + r = dns_label_unescape(&p, label, sizeof(label)); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + if (r > 3) + return -EINVAL; + + r = safe_atou8(label, &a[i]); + if (r < 0) + return r; + } + + r = dns_name_equal(p, "in-addr.arpa"); + if (r <= 0) + return r; + + *family = AF_INET; + address->in.s_addr = htobe32(((uint32_t) a[3] << 24) | + ((uint32_t) a[2] << 16) | + ((uint32_t) a[1] << 8) | + (uint32_t) a[0]); + + return 1; + } + + r = dns_name_endswith(p, "ip6.arpa"); + if (r < 0) + return r; + if (r > 0) { + struct in6_addr a; + unsigned i; + + for (i = 0; i < ELEMENTSOF(a.s6_addr); i++) { + char label[DNS_LABEL_MAX+1]; + int x, y; + + r = dns_label_unescape(&p, label, sizeof(label)); + if (r <= 0) + return r; + if (r != 1) + return -EINVAL; + x = unhexchar(label[0]); + if (x < 0) + return -EINVAL; + + r = dns_label_unescape(&p, label, sizeof(label)); + if (r <= 0) + return r; + if (r != 1) + return -EINVAL; + y = unhexchar(label[0]); + if (y < 0) + return -EINVAL; + + a.s6_addr[ELEMENTSOF(a.s6_addr) - i - 1] = (uint8_t) y << 4 | (uint8_t) x; + } + + r = dns_name_equal(p, "ip6.arpa"); + if (r <= 0) + return r; + + *family = AF_INET6; + address->in6 = a; + return 1; + } + + return 0; +} + +int dns_name_root(const char *name) { + char label[DNS_LABEL_MAX+1]; + int r; + + assert(name); + + r = dns_label_unescape(&name, label, sizeof(label)); + if (r < 0) + return r; + + return r == 0 && *name == 0; +} + +int dns_name_single_label(const char *name) { + char label[DNS_LABEL_MAX+1]; + int r; + + assert(name); + + r = dns_label_unescape(&name, label, sizeof(label)); + if (r < 0) + return r; + if (r == 0) + return 0; + + r = dns_label_unescape(&name, label, sizeof(label)); + if (r < 0) + return r; + + return r == 0 && *name == 0; +} diff --git a/src/resolve/resolved-dns-domain.h b/src/resolve/resolved-dns-domain.h new file mode 100644 index 0000000000..0888a7846f --- /dev/null +++ b/src/resolve/resolved-dns-domain.h @@ -0,0 +1,49 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. + ***/ + +#include <sys/types.h> + +#include "hashmap.h" +#include "in-addr-util.h" + +#define DNS_LABEL_MAX 63 +#define DNS_NAME_MAX 255 + +int dns_label_unescape(const char **name, char *dest, size_t sz); +int dns_label_escape(const char *p, size_t l, char **ret); + +int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); +int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); + +int dns_name_normalize(const char *s, char **_ret); + +unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_SIZE]); +int dns_name_compare_func(const void *a, const void *b); +extern const struct hash_ops dns_name_hash_ops; + +int dns_name_equal(const char *x, const char *y); +int dns_name_endswith(const char *name, const char *suffix); + +int dns_name_reverse(int family, const union in_addr_union *a, char **ret); +int dns_name_address(const char *p, int *family, union in_addr_union *a); + +int dns_name_root(const char *name); +int dns_name_single_label(const char *name); diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c new file mode 100644 index 0000000000..7375f77481 --- /dev/null +++ b/src/resolve/resolved-dns-packet.c @@ -0,0 +1,1461 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. + ***/ + +#include "utf8.h" +#include "util.h" +#include "strv.h" +#include "resolved-dns-domain.h" +#include "resolved-dns-packet.h" + +int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { + DnsPacket *p; + size_t a; + + assert(ret); + + if (mtu <= 0) + a = DNS_PACKET_SIZE_START; + else + a = mtu; + + if (a < DNS_PACKET_HEADER_SIZE) + a = DNS_PACKET_HEADER_SIZE; + + /* 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->n_ref = 1; + + *ret = p; + + return 0; +} + +int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { + DnsPacket *p; + DnsPacketHeader *h; + int r; + + assert(ret); + + r = dns_packet_new(&p, protocol, mtu); + if (r < 0) + return r; + + h = DNS_PACKET_HEADER(p); + + if (protocol == DNS_PROTOCOL_LLMNR) + 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 */)); + else + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, + 0 /* opcode */, + 0 /* aa */, + 0 /* tc */, + 1 /* rd (ask for recursion) */, + 0 /* ra */, + 0 /* ad */, + 0 /* cd */, + 0 /* rcode */)); + + *ret = p; + return 0; +} + +DnsPacket *dns_packet_ref(DnsPacket *p) { + + if (!p) + return NULL; + + assert(p->n_ref > 0); + p->n_ref++; + return p; +} + +static void dns_packet_free(DnsPacket *p) { + char *s; + + assert(p); + + dns_question_unref(p->question); + dns_answer_unref(p->answer); + + while ((s = hashmap_steal_first_key(p->names))) + free(s); + hashmap_free(p->names); + + free(p->_data); + free(p); +} + +DnsPacket *dns_packet_unref(DnsPacket *p) { + if (!p) + return NULL; + + assert(p->n_ref > 0); + + if (p->n_ref == 1) + dns_packet_free(p); + else + p->n_ref--; + + return NULL; +} + +int dns_packet_validate(DnsPacket *p) { + assert(p); + + if (p->size < DNS_PACKET_HEADER_SIZE) + return -EBADMSG; + + 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; + + /* RFC 4795, Section 2.1.1. says to discard all replies with QDCOUNT != 1 */ + if (p->protocol == DNS_PROTOCOL_LLMNR && + DNS_PACKET_QDCOUNT(p) != 1) + return -EBADMSG; + + 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; + + /* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */ + if (p->protocol == DNS_PROTOCOL_LLMNR && + 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; + + 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; +} + +static void dns_packet_truncate(DnsPacket *p, size_t sz) { + Iterator i; + char *s; + void *n; + + assert(p); + + if (p->size <= sz) + return; + + HASHMAP_FOREACH_KEY(s, n, p->names, i) { + + if (PTR_TO_SIZE(n) < sz) + continue; + + hashmap_remove(p->names, s); + free(s); + } + + p->size = sz; +} + +int dns_packet_append_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; + + ((uint8_t*) d)[0] = (uint8_t) (v >> 8); + ((uint8_t*) d)[1] = (uint8_t) v; + + return 0; +} + +int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start) { + void *d; + int r; + + assert(p); + + r = dns_packet_extend(p, sizeof(uint32_t), &d, start); + if (r < 0) + return r; + + ((uint8_t*) d)[0] = (uint8_t) (v >> 24); + ((uint8_t*) d)[1] = (uint8_t) (v >> 16); + ((uint8_t*) d)[2] = (uint8_t) (v >> 8); + ((uint8_t*) d)[3] = (uint8_t) v; + + return 0; +} + +int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start) { + void *d; + size_t l; + int r; + + assert(p); + assert(s); + + l = strlen(s); + if (l > 255) + return -E2BIG; + + r = dns_packet_extend(p, 1 + l, &d, start); + if (r < 0) + return r; + + ((uint8_t*) d)[0] = (uint8_t) l; + memcpy(((uint8_t*) d) + 1, s, l); + + return 0; +} + +int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start) { + void *w; + int r; + + assert(p); + assert(d); + + if (l > DNS_LABEL_MAX) + return -E2BIG; + + r = dns_packet_extend(p, 1 + l, &w, start); + if (r < 0) + return r; + + ((uint8_t*) w)[0] = (uint8_t) l; + memcpy(((uint8_t*) w) + 1, d, l); + + return 0; +} + +int dns_packet_append_name(DnsPacket *p, const char *name, + bool allow_compression, size_t *start) { + size_t saved_size; + int r; + + assert(p); + assert(name); + + saved_size = p->size; + + while (*name) { + _cleanup_free_ char *s = NULL; + char label[DNS_LABEL_MAX]; + size_t n = 0; + int k; + + 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; + } + } + + s = strdup(name); + if (!s) { + r = -ENOMEM; + goto fail; + } + + r = dns_label_unescape(&name, label, sizeof(label)); + if (r < 0) + goto fail; + + if (p->protocol == DNS_PROTOCOL_DNS) + k = dns_label_apply_idna(label, r, label, sizeof(label)); + else + k = dns_label_undo_idna(label, r, label, sizeof(label)); + if (k < 0) { + r = k; + goto fail; + } + if (k > 0) + r = k; + + r = dns_packet_append_label(p, label, r, &n); + if (r < 0) + goto fail; + + if (allow_compression) { + 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, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, k->type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, k->class, NULL); + if (r < 0) + goto fail; + + if (start) + *start = saved_size; + + return 0; + +fail: + dns_packet_truncate(p, saved_size); + return r; +} + +int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start) { + size_t saved_size, rdlength_offset, end, rdlength; + int r; + + assert(p); + assert(rr); + + saved_size = p->size; + + r = dns_packet_append_key(p, rr->key, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->ttl, NULL); + if (r < 0) + goto fail; + + /* Initially we write 0 here */ + r = dns_packet_append_uint16(p, 0, &rdlength_offset); + if (r < 0) + goto fail; + + switch (rr->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, NULL); + break; + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + r = dns_packet_append_name(p, rr->ptr.name, true, NULL); + 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: { + char **s; + + STRV_FOREACH(s, rr->txt.strings) { + r = dns_packet_append_string(p, *s, 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, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->soa.rname, true, 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, 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_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.key, rr->sshfp.key_size, NULL); + break; + + case DNS_TYPE_DNSKEY: + r = dns_packet_append_uint16(p, dnskey_to_flags(rr), NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, 3u, 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_uint8(p, rr->rrsig.key_tag, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->rrsig.signer, false, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->rrsig.signature, rr->rrsig.signature_size, NULL); + break; + + case _DNS_TYPE_INVALID: /* unparseable */ + default: + + r = dns_packet_append_blob(p, rr->generic.data, rr->generic.size, NULL); + break; + } + if (r < 0) + goto fail; + + /* Let's calculate the actual data size and update the field */ + rdlength = p->size - rdlength_offset - sizeof(uint16_t); + if (rdlength > 0xFFFF) { + r = ENOSPC; + goto fail; + } + + end = p->size; + p->size = rdlength_offset; + r = dns_packet_append_uint16(p, rdlength, NULL); + if (r < 0) + goto fail; + p->size = end; + + if (start) + *start = saved_size; + + return 0; + +fail: + dns_packet_truncate(p, saved_size); + return r; +} + + +int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start) { + assert(p); + + if (p->rindex + sz > p->size) + return -EMSGSIZE; + + if (ret) + *ret = (uint8_t*) DNS_PACKET_DATA(p) + p->rindex; + + if (start) + *start = p->rindex; + + p->rindex += sz; + return 0; +} + +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; +} + +int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start) { + const void *d; + int r; + + assert(p); + + r = dns_packet_read(p, sizeof(uint8_t), &d, start); + if (r < 0) + return r; + + *ret = ((uint8_t*) d)[0]; + return 0; +} + +int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start) { + const void *d; + int r; + + assert(p); + + r = dns_packet_read(p, sizeof(uint16_t), &d, start); + if (r < 0) + return r; + + *ret = (((uint16_t) ((uint8_t*) d)[0]) << 8) | + ((uint16_t) ((uint8_t*) d)[1]); + return 0; +} + +int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start) { + const void *d; + int r; + + assert(p); + + r = dns_packet_read(p, sizeof(uint32_t), &d, start); + if (r < 0) + return r; + + *ret = (((uint32_t) ((uint8_t*) d)[0]) << 24) | + (((uint32_t) ((uint8_t*) d)[1]) << 16) | + (((uint32_t) ((uint8_t*) d)[2]) << 8) | + ((uint32_t) ((uint8_t*) d)[3]); + + return 0; +} + +int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start) { + size_t saved_rindex; + const void *d; + char *t; + uint8_t c; + int r; + + assert(p); + + saved_rindex = p->rindex; + + r = dns_packet_read_uint8(p, &c, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read(p, c, &d, NULL); + if (r < 0) + goto fail; + + if (memchr(d, 0, c)) { + r = -EBADMSG; + goto fail; + } + + t = strndup(d, c); + if (!t) { + r = -ENOMEM; + goto fail; + } + + if (!utf8_is_valid(t)) { + free(t); + r = -EBADMSG; + goto fail; + } + + *ret = t; + + if (start) + *start = saved_rindex; + + return 0; + +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +int dns_packet_read_name(DnsPacket *p, char **_ret, + bool allow_compression, size_t *start) { + size_t saved_rindex, after_rindex = 0; + _cleanup_free_ char *ret = NULL; + size_t n = 0, allocated = 0; + bool first = true; + int r; + + assert(p); + assert(_ret); + + saved_rindex = p->rindex; + + for (;;) { + uint8_t c, d; + + r = dns_packet_read_uint8(p, &c, NULL); + if (r < 0) + goto fail; + + if (c == 0) + /* End of name */ + break; + else if (c <= 63) { + _cleanup_free_ char *t = NULL; + const char *label; + + /* Literal label */ + r = dns_packet_read(p, c, (const void**) &label, NULL); + if (r < 0) + goto fail; + + r = dns_label_escape(label, c, &t); + if (r < 0) + goto fail; + + if (!GREEDY_REALLOC(ret, allocated, n + !first + strlen(t) + 1)) { + r = -ENOMEM; + goto fail; + } + + if (!first) + ret[n++] = '.'; + else + first = false; + + memcpy(ret + n, t, 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) + goto fail; + + ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d; + if (ptr < DNS_PACKET_HEADER_SIZE || ptr >= saved_rindex) { + r = -EBADMSG; + goto fail; + } + + if (after_rindex == 0) + after_rindex = p->rindex; + + p->rindex = ptr; + } else + goto fail; + } + + if (!GREEDY_REALLOC(ret, allocated, n + 1)) { + r = -ENOMEM; + goto fail; + } + + ret[n] = 0; + + if (after_rindex != 0) + p->rindex= after_rindex; + + *_ret = ret; + ret = NULL; + + if (start) + *start = saved_rindex; + + return 0; + +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) { + _cleanup_free_ char *name = NULL; + uint16_t class, type; + DnsResourceKey *key; + size_t saved_rindex; + int r; + + assert(p); + assert(ret); + + saved_rindex = p->rindex; + + r = dns_packet_read_name(p, &name, true, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint16(p, &type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint16(p, &class, NULL); + if (r < 0) + goto fail; + + key = dns_resource_key_new_consume(class, type, name); + if (!key) { + r = -ENOMEM; + goto fail; + } + + name = NULL; + *ret = key; + + if (start) + *start = saved_rindex; + + return 0; +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +static int dns_packet_read_public_key(DnsPacket *p, size_t length, + void **dp, size_t *lengthp, + size_t *start) { + int r; + const void *d; + void *d2; + + r = dns_packet_read(p, length, &d, NULL); + if (r < 0) + return r; + + d2 = memdup(d, length); + if (!d2) + return -ENOMEM; + + *dp = d2; + *lengthp = length; + 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); +} + +static int dnskey_parse_flags(DnsResourceRecord *rr, uint16_t flags) { + assert(rr); + + if (flags & ~(DNSKEY_FLAG_SEP | DNSKEY_FLAG_ZONE_KEY)) + return -EBADMSG; + + rr->dnskey.zone_key_flag = flags & DNSKEY_FLAG_ZONE_KEY; + rr->dnskey.sep_flag = flags & DNSKEY_FLAG_SEP; + return 0; +} + +int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + size_t saved_rindex, offset; + uint16_t rdlength; + const void *d; + int r; + + assert(p); + assert(ret); + + saved_rindex = p->rindex; + + r = dns_packet_read_key(p, &key, NULL); + if (r < 0) + goto fail; + + if (key->class == DNS_CLASS_ANY || + key->type == DNS_TYPE_ANY) { + r = -EBADMSG; + goto fail; + } + + rr = dns_resource_record_new(key); + if (!rr) { + r = -ENOMEM; + goto fail; + } + + r = dns_packet_read_uint32(p, &rr->ttl, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint16(p, &rdlength, NULL); + if (r < 0) + goto fail; + + if (p->rindex + rdlength > p->size) { + r = -EBADMSG; + goto fail; + } + + offset = p->rindex; + + switch (rr->key->type) { + + case DNS_TYPE_SRV: + r = dns_packet_read_uint16(p, &rr->srv.priority, NULL); + if (r < 0) + goto fail; + r = dns_packet_read_uint16(p, &rr->srv.weight, NULL); + if (r < 0) + goto fail; + r = dns_packet_read_uint16(p, &rr->srv.port, NULL); + if (r < 0) + goto fail; + 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) + goto fail; + + r = dns_packet_read_string(p, &rr->hinfo.os, NULL); + break; + + case DNS_TYPE_SPF: /* exactly the same as TXT */ + case DNS_TYPE_TXT: { + char *s; + + while (p->rindex < offset + rdlength) { + r = dns_packet_read_string(p, &s, NULL); + if (r < 0) + goto fail; + + r = strv_consume(&rr->txt.strings, s); + if (r < 0) + goto fail; + } + + 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) + goto fail; + + r = dns_packet_read_name(p, &rr->soa.rname, true, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->soa.serial, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->soa.refresh, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->soa.retry, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->soa.expire, NULL); + if (r < 0) + goto fail; + + 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) + goto fail; + + 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) + goto fail; + + if (t == 0) { + rr->loc.version = t; + + r = dns_packet_read_uint8(p, &rr->loc.size, NULL); + if (r < 0) + goto fail; + + if (!loc_size_ok(rr->loc.size)) { + r = -EBADMSG; + goto fail; + } + + r = dns_packet_read_uint8(p, &rr->loc.horiz_pre, NULL); + if (r < 0) + goto fail; + + if (!loc_size_ok(rr->loc.horiz_pre)) { + r = -EBADMSG; + goto fail; + } + + r = dns_packet_read_uint8(p, &rr->loc.vert_pre, NULL); + if (r < 0) + goto fail; + + if (!loc_size_ok(rr->loc.vert_pre)) { + r = -EBADMSG; + goto fail; + } + + r = dns_packet_read_uint32(p, &rr->loc.latitude, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->loc.longitude, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->loc.altitude, NULL); + if (r < 0) + goto fail; + + break; + } else { + dns_packet_rewind(p, pos); + rr->unparseable = true; + goto unparseable; + } + } + + case DNS_TYPE_SSHFP: + r = dns_packet_read_uint8(p, &rr->sshfp.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->sshfp.fptype, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_public_key(p, rdlength - 2, + &rr->sshfp.key, &rr->sshfp.key_size, + NULL); + break; + + case DNS_TYPE_DNSKEY: { + uint16_t flags; + uint8_t proto; + + r = dns_packet_read_uint16(p, &flags, NULL); + if (r < 0) + goto fail; + + r = dnskey_parse_flags(rr, flags); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &proto, NULL); + if (r < 0) + goto fail; + + /* protocol is required to be always 3 */ + if (proto != 3) { + r = -EBADMSG; + goto fail; + } + + r = dns_packet_read_uint8(p, &rr->dnskey.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_public_key(p, rdlength - 4, + &rr->dnskey.key, &rr->dnskey.key_size, + NULL); + break; + } + + case DNS_TYPE_RRSIG: + r = dns_packet_read_uint16(p, &rr->rrsig.type_covered, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->rrsig.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->rrsig.labels, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->rrsig.original_ttl, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->rrsig.expiration, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->rrsig.inception, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint16(p, &rr->rrsig.key_tag, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_name(p, &rr->rrsig.signer, false, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_public_key(p, offset + rdlength - p->rindex, + &rr->rrsig.signature, &rr->rrsig.signature_size, + NULL); + break; + + default: + unparseable: + r = dns_packet_read(p, rdlength, &d, NULL); + if (r < 0) + goto fail; + + rr->generic.data = memdup(d, rdlength); + if (!rr->generic.data) { + r = -ENOMEM; + goto fail; + } + + rr->generic.size = rdlength; + break; + } + if (r < 0) + goto fail; + if (p->rindex != offset + rdlength) { + r = -EBADMSG; + goto fail; + } + + *ret = rr; + rr = NULL; + + if (start) + *start = saved_rindex; + + return 0; +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +int dns_packet_extract(DnsPacket *p) { + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + size_t saved_rindex; + unsigned n, i; + int r; + + if (p->extracted) + return 0; + + saved_rindex = p->rindex; + dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE); + + n = DNS_PACKET_QDCOUNT(p); + if (n > 0) { + question = dns_question_new(n); + if (!question) { + r = -ENOMEM; + goto finish; + } + + for (i = 0; i < n; i++) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + + r = dns_packet_read_key(p, &key, NULL); + if (r < 0) + goto finish; + + r = dns_question_add(question, key); + if (r < 0) + goto finish; + } + } + + n = DNS_PACKET_RRCOUNT(p); + if (n > 0) { + answer = dns_answer_new(n); + if (!answer) { + r = -ENOMEM; + goto finish; + } + + for (i = 0; i < n; i++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + r = dns_packet_read_rr(p, &rr, NULL); + if (r < 0) + goto finish; + + r = dns_answer_add(answer, rr); + if (r < 0) + goto finish; + } + } + + p->question = question; + question = NULL; + + p->answer = answer; + answer = NULL; + + p->extracted = true; + + r = 0; + +finish: + p->rindex = saved_rindex; + return r; +} + +static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = { + [DNS_RCODE_SUCCESS] = "SUCCESS", + [DNS_RCODE_FORMERR] = "FORMERR", + [DNS_RCODE_SERVFAIL] = "SERVFAIL", + [DNS_RCODE_NXDOMAIN] = "NXDOMAIN", + [DNS_RCODE_NOTIMP] = "NOTIMP", + [DNS_RCODE_REFUSED] = "REFUSED", + [DNS_RCODE_YXDOMAIN] = "YXDOMAIN", + [DNS_RCODE_YXRRSET] = "YRRSET", + [DNS_RCODE_NXRRSET] = "NXRRSET", + [DNS_RCODE_NOTAUTH] = "NOTAUTH", + [DNS_RCODE_NOTZONE] = "NOTZONE", + [DNS_RCODE_BADVERS] = "BADVERS", + [DNS_RCODE_BADKEY] = "BADKEY", + [DNS_RCODE_BADTIME] = "BADTIME", + [DNS_RCODE_BADMODE] = "BADMODE", + [DNS_RCODE_BADNAME] = "BADNAME", + [DNS_RCODE_BADALG] = "BADALG", + [DNS_RCODE_BADTRUNC] = "BADTRUNC", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_rcode, int); + +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); + +static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = { + [DNSSEC_ALGORITHM_RSAMD5] = "RSAMD5", + [DNSSEC_ALGORITHM_DH] = "DH", + [DNSSEC_ALGORITHM_DSA] = "DSA", + [DNSSEC_ALGORITHM_ECC] = "ECC", + [DNSSEC_ALGORITHM_RSASHA1] = "RSASHA1", + [DNSSEC_ALGORITHM_INDIRECT] = "INDIRECT", + [DNSSEC_ALGORITHM_PRIVATEDNS] = "PRIVATEDNS", + [DNSSEC_ALGORITHM_PRIVATEOID] = "PRIVATEOID", +}; +DEFINE_STRING_TABLE_LOOKUP(dnssec_algorithm, int); diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h new file mode 100644 index 0000000000..561dd3adfa --- /dev/null +++ b/src/resolve/resolved-dns-packet.h @@ -0,0 +1,236 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. + ***/ + +#include <inttypes.h> + +#include "macro.h" +#include "sparse-endian.h" +#include "hashmap.h" +#include "in-addr-util.h" + +typedef struct DnsPacketHeader DnsPacketHeader; +typedef struct DnsPacket DnsPacket; + +#include "resolved-dns-rr.h" +#include "resolved-dns-question.h" +#include "resolved-dns-answer.h" +#include "resolved-def.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) + +/* 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 + +#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 */ + + /* Parsed data */ + DnsQuestion *question; + DnsAnswer *answer; + + /* Packet reception meta data */ + int ifindex; + int family, ipproto; + union in_addr_union sender, destination; + uint16_t sender_port, destination_port; + uint32_t ttl; + + bool extracted; +}; + +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_RCODE(p) (be16toh(DNS_PACKET_HEADER(p)->flags) & 15) +#define DNS_PACKET_TC(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 9) & 1) +#define DNS_PACKET_C(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 10) & 1) +#define DNS_PACKET_T(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 8) & 1) +#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) | \ + ((uint16_t) !!tc << 9) | \ + ((uint16_t) !!rd << 8) | \ + ((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); + +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_append_blob(DnsPacket *p, const void *d, size_t sz, size_t *start); +int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start); +int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start); +int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start); +int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start); +int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, size_t *start); +int dns_packet_append_name(DnsPacket *p, const char *name, + bool allow_compression, size_t *start); +int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *key, size_t *start); +int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start); + +int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start); +int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start); +int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start); +int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start); +int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start); +int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start); +int dns_packet_read_name(DnsPacket *p, char **ret, + bool allow_compression, size_t *start); +int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start); +int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start); + +void dns_packet_rewind(DnsPacket *p, size_t idx); + +int dns_packet_skip_question(DnsPacket *p); +int dns_packet_extract(DnsPacket *p); + +enum { + DNS_RCODE_SUCCESS = 0, + DNS_RCODE_FORMERR = 1, + DNS_RCODE_SERVFAIL = 2, + DNS_RCODE_NXDOMAIN = 3, + DNS_RCODE_NOTIMP = 4, + DNS_RCODE_REFUSED = 5, + DNS_RCODE_YXDOMAIN = 6, + DNS_RCODE_YXRRSET = 7, + DNS_RCODE_NXRRSET = 8, + DNS_RCODE_NOTAUTH = 9, + DNS_RCODE_NOTZONE = 10, + DNS_RCODE_BADVERS = 16, + DNS_RCODE_BADSIG = 16, /* duplicate value! */ + DNS_RCODE_BADKEY = 17, + DNS_RCODE_BADTIME = 18, + DNS_RCODE_BADMODE = 19, + DNS_RCODE_BADNAME = 20, + DNS_RCODE_BADALG = 21, + DNS_RCODE_BADTRUNC = 22, + _DNS_RCODE_MAX_DEFINED +}; + +const char* dns_rcode_to_string(int i) _const_; +int dns_rcode_from_string(const char *s) _pure_; + +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 DNSKEY_FLAG_ZONE_KEY (1u << 8) +#define DNSKEY_FLAG_SEP (1u << 0) + +static inline uint16_t dnskey_to_flags(const DnsResourceRecord *rr) { + return (rr->dnskey.zone_key_flag * DNSKEY_FLAG_ZONE_KEY | + rr->dnskey.sep_flag * DNSKEY_FLAG_SEP); +} + +/* http://tools.ietf.org/html/rfc4034#appendix-A.1 */ +enum { + DNSSEC_ALGORITHM_RSAMD5 = 1, + DNSSEC_ALGORITHM_DH, + DNSSEC_ALGORITHM_DSA, + DNSSEC_ALGORITHM_ECC, + DNSSEC_ALGORITHM_RSASHA1, + DNSSEC_ALGORITHM_INDIRECT = 252, + DNSSEC_ALGORITHM_PRIVATEDNS, + DNSSEC_ALGORITHM_PRIVATEOID, + _DNSSEC_ALGORITHM_MAX_DEFINED +}; + +const char* dnssec_algorithm_to_string(int i) _const_; +int dnssec_algorithm_from_string(const char *s) _pure_; + +static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family) { + + /* Converts a protocol + family into a flags field as used in queries */ + + if (protocol == DNS_PROTOCOL_DNS) + return SD_RESOLVED_DNS; + + if (protocol == DNS_PROTOCOL_LLMNR) + return family == AF_INET6 ? SD_RESOLVED_LLMNR_IPV6 : SD_RESOLVED_LLMNR_IPV4; + + return 0; +} diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c new file mode 100644 index 0000000000..f0483c9806 --- /dev/null +++ b/src/resolve/resolved-dns-query.c @@ -0,0 +1,489 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "af-list.h" + +#include "resolved-dns-query.h" +#include "resolved-dns-domain.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 + +static void dns_query_stop(DnsQuery *q) { + DnsTransaction *t; + + assert(q); + + q->timeout_event_source = sd_event_source_unref(q->timeout_event_source); + + while ((t = set_steal_first(q->transactions))) { + set_remove(t->queries, q); + dns_transaction_gc(t); + } +} + +DnsQuery *dns_query_free(DnsQuery *q) { + if (!q) + return NULL; + + dns_query_stop(q); + set_free(q->transactions); + + dns_question_unref(q->question); + dns_answer_unref(q->answer); + + sd_bus_message_unref(q->request); + sd_bus_track_unref(q->bus_track); + + if (q->manager) { + LIST_REMOVE(queries, q->manager->dns_queries, q); + q->manager->n_dns_queries--; + } + + free(q); + + return NULL; +} + +int dns_query_new(Manager *m, DnsQuery **ret, DnsQuestion *question, int ifindex, uint64_t flags) { + _cleanup_(dns_query_freep) DnsQuery *q = NULL; + unsigned i; + int r; + + assert(m); + assert(question); + + r = dns_question_is_valid(question); + if (r < 0) + return r; + + if (m->n_dns_queries >= QUERIES_MAX) + return -EBUSY; + + q = new0(DnsQuery, 1); + if (!q) + return -ENOMEM; + + q->question = dns_question_ref(question); + q->ifindex = ifindex; + q->flags = flags; + + for (i = 0; i < question->n_keys; i++) { + _cleanup_free_ char *p; + + r = dns_resource_key_to_string(question->keys[i], &p); + if (r < 0) + return r; + + log_debug("Looking up RR for %s", p); + } + + LIST_PREPEND(queries, m->dns_queries, q); + m->n_dns_queries++; + q->manager = m; + + if (ret) + *ret = q; + q = NULL; + + return 0; +} + +static void dns_query_complete(DnsQuery *q, DnsTransactionState state) { + assert(q); + assert(!IN_SET(state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING)); + assert(IN_SET(q->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING)); + + /* 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_transaction(DnsQuery *q, DnsScope *s, DnsResourceKey *key) { + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + DnsTransaction *t; + int r; + + assert(q); + assert(s); + + r = set_ensure_allocated(&q->transactions, NULL); + if (r < 0) + return r; + + if (key) { + question = dns_question_new(1); + if (!question) + return -ENOMEM; + + r = dns_question_add(question, key); + if (r < 0) + return r; + } else + question = dns_question_ref(q->question); + + t = dns_scope_find_transaction(s, question, true); + if (!t) { + r = dns_transaction_new(&t, s, question); + if (r < 0) + return r; + } + + r = set_ensure_allocated(&t->queries, NULL); + if (r < 0) + goto gc; + + r = set_put(t->queries, q); + if (r < 0) + goto gc; + + r = set_put(q->transactions, t); + if (r < 0) { + set_remove(t->queries, q); + goto gc; + } + + return 0; + +gc: + dns_transaction_gc(t); + return r; +} + +static int dns_query_add_transaction_split(DnsQuery *q, DnsScope *s) { + int r; + + assert(q); + assert(s); + + if (s->protocol == DNS_PROTOCOL_MDNS) { + r = dns_query_add_transaction(q, s, NULL); + if (r < 0) + return r; + } else { + unsigned i; + + /* On DNS and LLMNR we can only send a single + * question per datagram, hence issue multiple + * transactions. */ + + for (i = 0; i < q->question->n_keys; i++) { + r = dns_query_add_transaction(q, s, q->question->keys[i]); + if (r < 0) + return r; + } + } + + return 0; +} + +int dns_query_go(DnsQuery *q) { + DnsScopeMatch found = DNS_SCOPE_NO; + DnsScope *s, *first = NULL; + DnsTransaction *t; + const char *name; + Iterator i; + int r; + + assert(q); + + if (q->state != DNS_TRANSACTION_NULL) + return 0; + + assert(q->question); + assert(q->question->n_keys > 0); + + name = DNS_RESOURCE_KEY_NAME(q->question->keys[0]); + + LIST_FOREACH(scopes, s, q->manager->dns_scopes) { + DnsScopeMatch match; + + 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) + return -ESRCH; + + r = dns_query_add_transaction_split(q, first); + if (r < 0) + goto fail; + + LIST_FOREACH(scopes, s, first->scopes_next) { + DnsScopeMatch match; + + match = dns_scope_good_domain(s, q->ifindex, q->flags, name); + if (match < 0) + goto fail; + + if (match != found) + continue; + + r = dns_query_add_transaction_split(q, s); + if (r < 0) + goto fail; + } + + q->answer = dns_answer_unref(q->answer); + q->answer_ifindex = 0; + q->answer_rcode = 0; + q->answer_family = AF_UNSPEC; + q->answer_protocol = _DNS_PROTOCOL_INVALID; + + 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; + + q->state = DNS_TRANSACTION_PENDING; + q->block_ready++; + + /* Start the transactions that are not started yet */ + SET_FOREACH(t, q->transactions, i) { + if (t->state != DNS_TRANSACTION_NULL) + continue; + + r = dns_transaction_go(t); + if (r < 0) + goto fail; + } + + q->block_ready--; + dns_query_ready(q); + + return 1; + +fail: + dns_query_stop(q); + return r; +} + +void dns_query_ready(DnsQuery *q) { + DnsTransaction *t; + DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + int rcode = 0; + DnsScope *scope = NULL; + bool pending = false; + Iterator i; + + assert(q); + assert(IN_SET(q->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING)); + + /* 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; + + SET_FOREACH(t, q->transactions, i) { + + /* If we found a successful answer, ignore all answers from other scopes */ + if (state == DNS_TRANSACTION_SUCCESS && t->scope != scope) + continue; + + /* One of the transactions is still going on, let's maybe wait for it */ + if (IN_SET(t->state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_NULL)) { + pending = true; + continue; + } + + /* One of the transactions is successful, let's use + * it, and copy its data out */ + if (t->state == DNS_TRANSACTION_SUCCESS) { + DnsAnswer *a; + + if (t->received) { + rcode = DNS_PACKET_RCODE(t->received); + a = t->received->answer; + } else { + rcode = t->cached_rcode; + a = t->cached; + } + + if (state == DNS_TRANSACTION_SUCCESS) { + DnsAnswer *merged; + + merged = dns_answer_merge(answer, a); + if (!merged) { + dns_query_complete(q, DNS_TRANSACTION_RESOURCES); + return; + } + + dns_answer_unref(answer); + answer = merged; + } else { + dns_answer_unref(answer); + answer = dns_answer_ref(a); + } + + scope = t->scope; + state = DNS_TRANSACTION_SUCCESS; + continue; + } + + /* One of the transactions has failed, let's see + * whether we find anything better, but if not, return + * its response data */ + if (state != DNS_TRANSACTION_SUCCESS && t->state == DNS_TRANSACTION_FAILURE) { + DnsAnswer *a; + + if (t->received) { + rcode = DNS_PACKET_RCODE(t->received); + a = t->received->answer; + } else { + rcode = t->cached_rcode; + a = t->cached; + } + + dns_answer_unref(answer); + answer = dns_answer_ref(a); + + scope = t->scope; + state = DNS_TRANSACTION_FAILURE; + continue; + } + + if (state == DNS_TRANSACTION_NO_SERVERS && t->state != DNS_TRANSACTION_NO_SERVERS) + state = t->state; + } + + if (pending) { + + /* If so far we weren't successful, and there's + * something still pending, then wait for it */ + if (state != DNS_TRANSACTION_SUCCESS) + return; + + /* If we already were successful, then only wait for + * other transactions on the same scope to finish. */ + SET_FOREACH(t, q->transactions, i) { + if (t->scope == scope && IN_SET(t->state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_NULL)) + return; + } + } + + if (IN_SET(state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_FAILURE)) { + q->answer = dns_answer_ref(answer); + q->answer_rcode = rcode; + q->answer_ifindex = (scope && scope->link) ? scope->link->ifindex : 0; + q->answer_protocol = scope ? scope->protocol : _DNS_PROTOCOL_INVALID; + q->answer_family = scope ? scope->family : AF_UNSPEC; + } + + dns_query_complete(q, state); +} + +int dns_query_cname_redirect(DnsQuery *q, const char *name) { + _cleanup_(dns_question_unrefp) DnsQuestion *nq = NULL; + int r; + + assert(q); + + if (q->n_cname_redirects > CNAME_MAX) + return -ELOOP; + + r = dns_question_cname_redirect(q->question, name, &nq); + if (r < 0) + return r; + + dns_question_unref(q->question); + q->question = nq; + nq = NULL; + + q->n_cname_redirects++; + + dns_query_stop(q); + q->state = DNS_TRANSACTION_NULL; + + return 0; +} + +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 *bus, sd_bus_message *m) { + int r; + + assert(q); + assert(m); + + if (!q->bus_track) { + r = sd_bus_track_new(bus, &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; +} diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h new file mode 100644 index 0000000000..13b3ee4f81 --- /dev/null +++ b/src/resolve/resolved-dns-query.h @@ -0,0 +1,86 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> + +#include "sd-bus.h" +#include "util.h" +#include "set.h" + +typedef struct DnsQuery DnsQuery; + +#include "resolved-dns-scope.h" +#include "resolved-dns-rr.h" +#include "resolved-dns-question.h" +#include "resolved-dns-answer.h" +#include "resolved-dns-stream.h" +#include "resolved-dns-transaction.h" +#include "resolved-manager.h" + +struct DnsQuery { + Manager *manager; + DnsQuestion *question; + + uint64_t flags; + int ifindex; + + DnsTransactionState state; + unsigned n_cname_redirects; + + sd_event_source *timeout_event_source; + + /* Discovered data */ + DnsAnswer *answer; + int answer_ifindex; + int answer_family; + DnsProtocol answer_protocol; + int answer_rcode; + + /* Bus client information */ + sd_bus_message *request; + int request_family; + const char *request_hostname; + union in_addr_union request_address; + + /* Completion callback */ + void (*complete)(DnsQuery* q); + unsigned block_ready; + + Set *transactions; + + sd_bus_track *bus_track; + + LIST_FIELDS(DnsQuery, queries); +}; + +int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question, int family, uint64_t flags); +DnsQuery *dns_query_free(DnsQuery *q); + +int dns_query_go(DnsQuery *q); +void dns_query_ready(DnsQuery *q); + +int dns_query_cname_redirect(DnsQuery *q, const char *name); + +int dns_query_bus_track(DnsQuery *q, sd_bus *bus, sd_bus_message *m); + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuery*, dns_query_free); diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c new file mode 100644 index 0000000000..45bcbbf23a --- /dev/null +++ b/src/resolve/resolved-dns-question.c @@ -0,0 +1,274 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "resolved-dns-question.h" +#include "resolved-dns-domain.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(q); + assert(key); + + 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) { + unsigned i; + int r; + + assert(q); + assert(rr); + + for (i = 0; i < q->n_keys; i++) { + r = dns_resource_key_match_rr(q->keys[i], rr); + if (r != 0) + return r; + } + + return 0; +} + +int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr) { + unsigned i; + int r; + + assert(q); + assert(rr); + + for (i = 0; i < q->n_keys; i++) { + r = dns_resource_key_match_cname(q->keys[i], rr); + if (r != 0) + return r; + } + + return 0; +} + +int dns_question_is_valid(DnsQuestion *q) { + const char *name; + unsigned i; + int r; + + assert(q); + + 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 = 1; i < q->n_keys; i++) { + assert(q->keys[i]); + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(q->keys[i]), name); + if (r <= 0) + return r; + } + + return 1; +} + +int dns_question_is_superset(DnsQuestion *q, DnsQuestion *other) { + unsigned j; + int r; + + assert(q); + assert(other); + + /* Checks if all keys in "other" are also contained in "q" */ + + for (j = 0; j < other->n_keys; j++) { + DnsResourceKey *b = other->keys[j]; + bool found = false; + unsigned i; + + for (i = 0; i < q->n_keys; i++) { + DnsResourceKey *a = q->keys[i]; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(a), DNS_RESOURCE_KEY_NAME(b)); + if (r < 0) + return r; + + if (r == 0) + continue; + + if (a->class != b->class && a->class != DNS_CLASS_ANY) + continue; + + if (a->type != b->type && a->type != DNS_TYPE_ANY) + continue; + + found = true; + break; + } + + if (!found) + return 0; + } + + return 1; +} + +int dns_question_cname_redirect(DnsQuestion *q, const char *name, DnsQuestion **ret) { + _cleanup_(dns_question_unrefp) DnsQuestion *n = NULL; + bool same = true; + unsigned i; + int r; + + assert(q); + assert(name); + assert(ret); + + for (i = 0; i < q->n_keys; i++) { + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(q->keys[i]), name); + if (r < 0) + return r; + + if (r == 0) { + same = false; + break; + } + } + + if (same) { + /* Shortcut, the names are already right */ + *ret = dns_question_ref(q); + return 0; + } + + n = dns_question_new(q->n_keys); + if (!n) + return -ENOMEM; + + /* Create a new question, and patch in the new name */ + for (i = 0; i < q->n_keys; i++) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; + + k = dns_resource_key_new(q->keys[i]->class, q->keys[i]->type, name); + if (!k) + return -ENOMEM; + + r = dns_question_add(n, k); + if (r < 0) + return r; + } + + *ret = n; + n = NULL; + + return 1; +} + +int dns_question_endswith(DnsQuestion *q, const char *suffix) { + unsigned i; + + assert(q); + assert(suffix); + + for (i = 0; i < q->n_keys; i++) { + int k; + + k = dns_name_endswith(DNS_RESOURCE_KEY_NAME(q->keys[i]), suffix); + if (k <= 0) + return k; + } + + return 1; +} + +int dns_question_extract_reverse_address(DnsQuestion *q, int *family, union in_addr_union *address) { + unsigned i; + + assert(q); + assert(family); + assert(address); + + for (i = 0; i < q->n_keys; i++) { + int k; + + k = dns_name_address(DNS_RESOURCE_KEY_NAME(q->keys[i]), family, address); + if (k != 0) + return k; + } + + return 0; +} diff --git a/src/resolve/resolved-dns-question.h b/src/resolve/resolved-dns-question.h new file mode 100644 index 0000000000..4ba2fe9f0e --- /dev/null +++ b/src/resolve/resolved-dns-question.h @@ -0,0 +1,52 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +typedef struct DnsQuestion DnsQuestion; + +#include "resolved-dns-rr.h" + +/* A simple array of resources 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_add(DnsQuestion *q, DnsResourceKey *key); + +int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr); +int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr); +int dns_question_is_valid(DnsQuestion *q); +int dns_question_is_superset(DnsQuestion *q, DnsQuestion *other); + +int dns_question_cname_redirect(DnsQuestion *q, const char *name, DnsQuestion **ret); + +int dns_question_endswith(DnsQuestion *q, const char *suffix); +int dns_question_extract_reverse_address(DnsQuestion *q, int *family, union in_addr_union *address); + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuestion*, dns_question_unref); diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c new file mode 100644 index 0000000000..fd5ecf413d --- /dev/null +++ b/src/resolve/resolved-dns-rr.c @@ -0,0 +1,704 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <math.h> + +#include "strv.h" + +#include "resolved-dns-domain.h" +#include "resolved-dns-rr.h" +#include "resolved-dns-packet.h" +#include "dns-type.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_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; + + 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 > 0); + + if (k->n_ref == 1) { + free(k->_name); + free(k); + } else + k->n_ref--; + + return NULL; +} + +int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) { + int r; + + 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, const DnsResourceRecord *rr) { + assert(key); + assert(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; + + return dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key)); +} + +int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRecord *rr) { + assert(key); + assert(rr); + + if (rr->key->class != key->class && key->class != DNS_CLASS_ANY) + return 0; + + if (rr->key->type != DNS_TYPE_CNAME) + return 0; + + return dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key)); +} + +static unsigned long dns_resource_key_hash_func(const void *i, const uint8_t hash_key[HASH_KEY_SIZE]) { + const DnsResourceKey *k = i; + unsigned long ul; + + ul = dns_name_hash_func(DNS_RESOURCE_KEY_NAME(k), hash_key); + ul = ul * hash_key[0] + ul + k->class; + ul = ul * hash_key[1] + ul + k->type; + + return ul; +} + +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 +}; + +int dns_resource_key_to_string(const DnsResourceKey *key, char **ret) { + char cbuf[DECIMAL_STR_MAX(uint16_t)], tbuf[DECIMAL_STR_MAX(uint16_t)]; + const char *c, *t; + char *s; + + c = dns_class_to_string(key->class); + if (!c) { + sprintf(cbuf, "%i", key->class); + c = cbuf; + } + + t = dns_type_to_string(key->type); + if (!t){ + sprintf(tbuf, "%i", key->type); + t = tbuf; + } + + if (asprintf(&s, "%s %s %-5s", DNS_RESOURCE_KEY_NAME(key), c, t) < 0) + return -ENOMEM; + + *ret = s; + return 0; +} + +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); + + 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: + strv_free(rr->txt.strings); + 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_SSHFP: + free(rr->sshfp.key); + 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_LOC: + case DNS_TYPE_A: + case DNS_TYPE_AAAA: + break; + + default: + free(rr->generic.data); + } + + dns_resource_key_unref(rr->key); + } + + free(rr); + + return NULL; +} + +int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *hostname) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_free_ char *ptr = NULL; + int r; + + assert(ret); + assert(address); + assert(hostname); + + r = dns_name_reverse(family, address, &ptr); + if (r < 0) + return r; + + key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, ptr); + if (!key) + return -ENOMEM; + + ptr = NULL; + + rr = dns_resource_record_new(key); + if (!rr) + return -ENOMEM; + + rr->ptr.name = strdup(hostname); + if (!rr->ptr.name) + return -ENOMEM; + + *ret = rr; + rr = NULL; + + return 0; +} + +int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecord *b) { + int r; + + assert(a); + assert(b); + + 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: { + int i; + + for (i = 0; a->txt.strings[i] || b->txt.strings[i]; i++) + if (!streq_ptr(a->txt.strings[i], b->txt.strings[i])) + return false; + return true; + } + + 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_SSHFP: + return a->sshfp.algorithm == b->sshfp.algorithm && + a->sshfp.fptype == b->sshfp.fptype && + a->sshfp.key_size == b->sshfp.key_size && + memcmp(a->sshfp.key, b->sshfp.key, a->sshfp.key_size) == 0; + + case DNS_TYPE_DNSKEY: + return a->dnskey.zone_key_flag == b->dnskey.zone_key_flag && + a->dnskey.sep_flag == b->dnskey.sep_flag && + a->dnskey.algorithm == b->dnskey.algorithm && + a->dnskey.key_size == b->dnskey.key_size && + memcmp(a->dnskey.key, b->dnskey.key, a->dnskey.key_size) == 0; + + case DNS_TYPE_RRSIG: + /* do the fast comparisons first */ + if (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 || + a->rrsig.signature_size != b->rrsig.signature_size || + memcmp(a->rrsig.signature, b->rrsig.signature, a->rrsig.signature_size) != 0) + return false; + + return dns_name_equal(a->rrsig.signer, b->rrsig.signer); + + default: + return a->generic.size == b->generic.size && + memcmp(a->generic.data, b->generic.data, a->generic.size) == 0; + } +} + +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; +} + +int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { + _cleanup_free_ char *k = NULL, *t = NULL; + char *s; + int r; + + assert(rr); + + r = dns_resource_key_to_string(rr->key, &k); + if (r < 0) + return r; + + 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 -ENOMEM; + 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 -ENOMEM; + + break; + + case DNS_TYPE_HINFO: + s = strjoin(k, " ", rr->hinfo.cpu, " ", rr->hinfo.os, NULL); + if (!s) + return -ENOMEM; + break; + + case DNS_TYPE_SPF: /* exactly the same as TXT */ + case DNS_TYPE_TXT: + t = strv_join_quoted(rr->txt.strings); + if (!t) + return -ENOMEM; + + s = strjoin(k, " ", t, NULL); + if (!s) + return -ENOMEM; + + 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 r; + + s = strjoin(k, " ", x, NULL); + if (!s) + return -ENOMEM; + break; + } + + case DNS_TYPE_AAAA: + r = in_addr_to_string(AF_INET6, (const union in_addr_union*) &rr->aaaa.in6_addr, &t); + if (r < 0) + return r; + + s = strjoin(k, " ", t, NULL); + if (!s) + return -ENOMEM; + 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 -ENOMEM; + break; + + case DNS_TYPE_MX: + r = asprintf(&s, "%s %u %s", + k, + rr->mx.priority, + rr->mx.exchange); + if (r < 0) + return -ENOMEM; + 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 -ENOMEM; + + s = strjoin(k, " ", t, NULL); + if (!s) + return -ENOMEM; + break; + + case DNS_TYPE_SSHFP: + t = hexmem(rr->sshfp.key, rr->sshfp.key_size); + if (!t) + return -ENOMEM; + + r = asprintf(&s, "%s %u %u %s", + k, + rr->sshfp.algorithm, + rr->sshfp.fptype, + t); + if (r < 0) + return -ENOMEM; + break; + + case DNS_TYPE_DNSKEY: { + const char *alg; + + alg = dnssec_algorithm_to_string(rr->dnskey.algorithm); + + t = hexmem(rr->dnskey.key, rr->dnskey.key_size); + if (!t) + return -ENOMEM; + + r = asprintf(&s, "%s %u 3 %.*s%.*u %s", + k, + dnskey_to_flags(rr), + alg ? -1 : 0, alg, + alg ? 0 : 1, alg ? 0u : (unsigned) rr->dnskey.algorithm, + t); + if (r < 0) + return -ENOMEM; + break; + } + + case DNS_TYPE_RRSIG: { + const char *type, *alg; + + type = dns_type_to_string(rr->rrsig.type_covered); + alg = dnssec_algorithm_to_string(rr->rrsig.algorithm); + + t = hexmem(rr->rrsig.signature, rr->rrsig.signature_size); + if (!t) + return -ENOMEM; + + /* TYPE?? follows + * http://tools.ietf.org/html/rfc3597#section-5 */ + + r = asprintf(&s, "%s %s%.*u %.*s%.*u %u %u %u %u %u %s %s", + k, + type ?: "TYPE", + type ? 0 : 1, type ? 0u : (unsigned) rr->rrsig.type_covered, + alg ? -1 : 0, alg, + alg ? 0 : 1, alg ? 0u : (unsigned) rr->rrsig.algorithm, + rr->rrsig.labels, + rr->rrsig.original_ttl, + rr->rrsig.expiration, + rr->rrsig.inception, + rr->rrsig.key_tag, + rr->rrsig.signer, + t); + if (r < 0) + return -ENOMEM; + break; + } + + default: + t = hexmem(rr->generic.data, rr->generic.size); + if (!t) + return -ENOMEM; + + s = strjoin(k, " ", t, NULL); + if (!s) + return -ENOMEM; + break; + } + + *ret = s; + return 0; +} + +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, uint16_t *class) { + assert(s); + assert(class); + + if (strcaseeq(s, "IN")) + *class = DNS_CLASS_IN; + else if (strcaseeq(s, "ANY")) + *class = DNS_TYPE_ANY; + else + return -EINVAL; + + return 0; +} diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h new file mode 100644 index 0000000000..9d9a89d383 --- /dev/null +++ b/src/resolve/resolved-dns-rr.h @@ -0,0 +1,177 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. + ***/ + +#include <inttypes.h> +#include <netinet/in.h> + +#include "util.h" +#include "hashmap.h" +#include "in-addr-util.h" +#include "dns-type.h" + +typedef struct DnsResourceKey DnsResourceKey; +typedef struct DnsResourceRecord DnsResourceRecord; + +/* DNS record classes, see RFC 1035 */ +enum { + DNS_CLASS_IN = 0x01, + DNS_CLASS_ANY = 0xFF, + _DNS_CLASS_MAX, + _DNS_CLASS_INVALID = -1 +}; + +struct DnsResourceKey { + unsigned n_ref; + uint16_t class, type; + char *_name; /* don't access directy, use DNS_RESOURCE_KEY_NAME()! */ +}; + +struct DnsResourceRecord { + unsigned n_ref; + DnsResourceKey *key; + uint32_t ttl; + bool unparseable; + union { + struct { + void *data; + uint16_t size; + } generic; + + struct { + uint16_t priority; + uint16_t weight; + uint16_t port; + char *name; + } srv; + + struct { + char *name; + } ptr, ns, cname, dname; + + struct { + char *cpu; + char *os; + } hinfo; + + struct { + char **strings; + } 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; + + 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; + + struct { + uint8_t algorithm; + uint8_t fptype; + void *key; + size_t key_size; + } sshfp; + + /* http://tools.ietf.org/html/rfc4034#section-2.1 */ + struct { + bool zone_key_flag:1; + bool sep_flag:1; + 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; + }; +}; + +static inline const char* DNS_RESOURCE_KEY_NAME(const DnsResourceKey *key) { + if (_unlikely_(!key)) + return NULL; + + if (key->_name) + return key->_name; + + return (char*) key + sizeof(DnsResourceKey); +} + +DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const 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); +int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b); +int dns_resource_key_match_rr(const DnsResourceKey *key, const DnsResourceRecord *rr); +int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRecord *rr); +int dns_resource_key_to_string(const DnsResourceKey *key, char **ret); +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceKey*, dns_resource_key_unref); + +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_equal(const DnsResourceRecord *a, const DnsResourceRecord *b); +int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret); +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceRecord*, dns_resource_record_unref); + +const char *dns_class_to_string(uint16_t type); +int dns_class_from_string(const char *name, uint16_t *class); + +extern const struct hash_ops dns_resource_key_hash_ops; diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c new file mode 100644 index 0000000000..1664b1328e --- /dev/null +++ b/src/resolve/resolved-dns-scope.c @@ -0,0 +1,797 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <netinet/tcp.h> + +#include "missing.h" +#include "strv.h" +#include "socket-util.h" +#include "af-list.h" +#include "resolved-dns-domain.h" +#include "resolved-dns-scope.h" + +#define MULTICAST_RATELIMIT_INTERVAL_USEC (1*USEC_PER_SEC) +#define MULTICAST_RATELIMIT_BURST 1000 + +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; + + LIST_PREPEND(scopes, m->dns_scopes, s); + + dns_scope_llmnr_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; +} + +DnsScope* dns_scope_free(DnsScope *s) { + DnsTransaction *t; + 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); + + while ((t = s->transactions)) { + + /* Abort the transaction, but make sure it is not + * freed while we still look at it */ + + t->block_gc++; + dns_transaction_complete(t, DNS_TRANSACTION_ABORTED); + t->block_gc--; + + dns_transaction_free(t); + } + + 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); + strv_free(s->domains); + free(s); + + return NULL; +} + +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); +} + +int dns_scope_emit(DnsScope *s, DnsPacket *p) { + union in_addr_union addr; + int ifindex = 0, r; + int family; + uint16_t port; + uint32_t mtu; + int fd; + + 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); + + if (s->protocol == DNS_PROTOCOL_DNS) { + DnsServer *srv; + + if (DNS_PACKET_QDCOUNT(p) > 1) + return -ENOTSUP; + + srv = dns_scope_get_dns_server(s); + if (!srv) + return -ESRCH; + + family = srv->family; + addr = srv->address; + port = 53; + + if (p->size > DNS_PACKET_UNICAST_SIZE_MAX) + return -EMSGSIZE; + + if (p->size > mtu) + return -EMSGSIZE; + + if (family == AF_INET) + fd = manager_dns_ipv4_fd(s->manager); + else if (family == AF_INET6) + fd = manager_dns_ipv6_fd(s->manager); + else + return -EAFNOSUPPORT; + if (fd < 0) + return fd; + + } else if (s->protocol == DNS_PROTOCOL_LLMNR) { + + if (DNS_PACKET_QDCOUNT(p) > 1) + return -ENOTSUP; + + if (!ratelimit_test(&s->ratelimit)) + return -EBUSY; + + family = s->family; + port = 5355; + + 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; + } else + return -EAFNOSUPPORT; + + r = manager_send(s->manager, fd, ifindex, family, &addr, port, p); + if (r < 0) + return r; + + return 1; +} + +int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *address, uint16_t port) { + _cleanup_close_ int fd = -1; + union sockaddr_union sa = {}; + socklen_t salen; + static const int one = 1; + int ret, r; + + assert(s); + assert((family == AF_UNSPEC) == !address); + + if (family == AF_UNSPEC) { + DnsServer *srv; + + srv = dns_scope_get_dns_server(s); + if (!srv) + return -ESRCH; + + sa.sa.sa_family = srv->family; + if (srv->family == AF_INET) { + sa.in.sin_port = htobe16(port); + sa.in.sin_addr = srv->address.in; + salen = sizeof(sa.in); + } else if (srv->family == AF_INET6) { + sa.in6.sin6_port = htobe16(port); + sa.in6.sin6_addr = srv->address.in6; + sa.in6.sin6_scope_id = s->link ? s->link->ifindex : 0; + salen = sizeof(sa.in6); + } else + return -EAFNOSUPPORT; + } else { + sa.sa.sa_family = family; + + if (family == AF_INET) { + sa.in.sin_port = htobe16(port); + sa.in.sin_addr = address->in; + salen = sizeof(sa.in); + } else if (family == AF_INET6) { + sa.in6.sin6_port = htobe16(port); + sa.in6.sin6_addr = address->in6; + sa.in6.sin6_scope_id = s->link ? s->link->ifindex : 0; + salen = sizeof(sa.in6); + } else + return -EAFNOSUPPORT; + } + + fd = socket(sa.sa.sa_family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) + return -errno; + + r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); + if (r < 0) + return -errno; + + if (s->link) { + uint32_t ifindex = htobe32(s->link->ifindex); + + if (sa.sa.sa_family == AF_INET) { + r = setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex)); + if (r < 0) + return -errno; + } else if (sa.sa.sa_family == AF_INET6) { + r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex)); + if (r < 0) + return -errno; + } + } + + if (s->protocol == DNS_PROTOCOL_LLMNR) { + /* RFC 4795, section 2.5 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; +} + +DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain) { + char **i; + + assert(s); + assert(domain); + + if (ifindex != 0 && (!s->link || s->link->ifindex != ifindex)) + return DNS_SCOPE_NO; + + if ((SD_RESOLVED_FLAGS_MAKE(s->protocol, s->family) & flags) == 0) + return DNS_SCOPE_NO; + + STRV_FOREACH(i, s->domains) + if (dns_name_endswith(domain, *i) > 0) + return DNS_SCOPE_YES; + + if (dns_name_root(domain) != 0) + return DNS_SCOPE_NO; + + if (is_localhost(domain)) + return DNS_SCOPE_NO; + + if (s->protocol == DNS_PROTOCOL_DNS) { + if (dns_name_endswith(domain, "254.169.in-addr.arpa") == 0 && + dns_name_endswith(domain, "0.8.e.f.ip6.arpa") == 0 && + dns_name_single_label(domain) == 0) + return DNS_SCOPE_MAYBE; + + return DNS_SCOPE_NO; + } + + if (s->protocol == DNS_PROTOCOL_MDNS) { + if (dns_name_endswith(domain, "254.169.in-addr.arpa") > 0 || + dns_name_endswith(domain, "0.8.e.f.ip6.arpa") > 0 || + (dns_name_endswith(domain, "local") > 0 && dns_name_equal(domain, "local") == 0)) + return DNS_SCOPE_MAYBE; + + return DNS_SCOPE_NO; + } + + if (s->protocol == DNS_PROTOCOL_LLMNR) { + if (dns_name_endswith(domain, "in-addr.arpa") > 0 || + dns_name_endswith(domain, "ip6.arpa") > 0 || + dns_name_single_label(domain) > 0) + return DNS_SCOPE_MAYBE; + + return DNS_SCOPE_NO; + } + + assert_not_reached("Unknown scope protocol"); +} + +int dns_scope_good_key(DnsScope *s, DnsResourceKey *key) { + assert(s); + assert(key); + + if (s->protocol == DNS_PROTOCOL_DNS) + return true; + + /* On mDNS and LLMNR, send A and AAAA queries only on the + * respective scopes */ + + if (s->family == AF_INET && key->class == DNS_CLASS_IN && key->type == DNS_TYPE_AAAA) + return false; + + if (s->family == AF_INET6 && key->class == DNS_CLASS_IN && key->type == DNS_TYPE_A) + return false; + + return true; +} + +int dns_scope_llmnr_membership(DnsScope *s, bool b) { + int fd; + + assert(s); + + if (s->protocol != DNS_PROTOCOL_LLMNR) + return 0; + + assert(s->link); + + if (s->family == AF_INET) { + struct ip_mreqn mreqn = { + .imr_multiaddr = LLMNR_MULTICAST_IPV4_ADDRESS, + .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) + 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 = LLMNR_MULTICAST_IPV6_ADDRESS, + .ipv6mr_interface = s->link->ifindex, + }; + + fd = manager_llmnr_ipv6_udp_fd(s->manager); + if (fd < 0) + return fd; + + if (b) + 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_good_dns_server(DnsScope *s, int family, const union in_addr_union *address) { + assert(s); + assert(address); + + if (s->protocol != DNS_PROTOCOL_DNS) + return 1; + + if (s->link) + return !!link_find_dns_server(s->link, family, address); + else + return !!manager_find_dns_server(s->manager, family, address); +} + +static int dns_scope_make_reply_packet( + DnsScope *s, + uint16_t id, + int rcode, + DnsQuestion *q, + DnsAnswer *answer, + DnsAnswer *soa, + bool tentative, + DnsPacket **ret) { + + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + unsigned i; + int r; + + assert(s); + assert(ret); + + if ((!q || q->n_keys <= 0) + && (!answer || answer->n_rrs <= 0) + && (!soa || soa->n_rrs <= 0)) + return -EINVAL; + + r = dns_packet_new(&p, s->protocol, 0); + if (r < 0) + return r; + + DNS_PACKET_HEADER(p)->id = id; + DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( + 1 /* qr */, + 0 /* opcode */, + 0 /* c */, + 0 /* tc */, + tentative, + 0 /* (ra) */, + 0 /* (ad) */, + 0 /* (cd) */, + rcode)); + + if (q) { + for (i = 0; i < q->n_keys; i++) { + r = dns_packet_append_key(p, q->keys[i], NULL); + if (r < 0) + return r; + } + + DNS_PACKET_HEADER(p)->qdcount = htobe16(q->n_keys); + } + + if (answer) { + for (i = 0; i < answer->n_rrs; i++) { + r = dns_packet_append_rr(p, answer->rrs[i], NULL); + if (r < 0) + return r; + } + + DNS_PACKET_HEADER(p)->ancount = htobe16(answer->n_rrs); + } + + if (soa) { + for (i = 0; i < soa->n_rrs; i++) { + r = dns_packet_append_rr(p, soa->rrs[i], NULL); + if (r < 0) + return r; + } + + DNS_PACKET_HEADER(p)->arcount = htobe16(soa->n_rrs); + } + + *ret = p; + p = NULL; + + return 0; +} + +static void dns_scope_verify_conflicts(DnsScope *s, DnsPacket *p) { + unsigned n; + + assert(s); + assert(p); + + if (p->question) + for (n = 0; n < p->question->n_keys; n++) + dns_zone_verify_conflicts(&s->zone, p->question->keys[n]); + if (p->answer) + for (n = 0; n < p->answer->n_rrs; n++) + dns_zone_verify_conflicts(&s->zone, p->answer->rrs[n]->key); +} + +void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) { + _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL; + bool tentative = false; + int r, fd; + + 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("Failed to extract resources from incoming packet: %s", strerror(-r)); + return; + } + + if (DNS_PACKET_C(p)) { + /* Somebody notified us about a possible conflict */ + dns_scope_verify_conflicts(s, p); + return; + } + + r = dns_zone_lookup(&s->zone, p->question, &answer, &soa, &tentative); + if (r < 0) { + log_debug("Failed to lookup key: %s", strerror(-r)); + 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("Failed to build reply packet: %s", strerror(-r)); + return; + } + + if (stream) + r = dns_stream_write_packet(stream, reply); + else { + 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("Failed to get reply socket: %s", strerror(-fd)); + 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, reply); + } + + if (r < 0) { + log_debug("Failed to send reply packet: %s", strerror(-r)); + return; + } +} + +DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsQuestion *question, bool cache_ok) { + DnsTransaction *t; + + assert(scope); + assert(question); + + /* Try to find an ongoing transaction that is a equal or a + * superset of the specified question */ + + LIST_FOREACH(transactions_by_scope, t, scope->transactions) { + + /* Refuse reusing transactions that completed based on + * cached data instead of a real packet, if that's + * requested. */ + if (!cache_ok && + IN_SET(t->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_FAILURE) && + !t->received) + continue; + + if (dns_question_is_superset(t->question, question) > 0) + return t; + } + + return NULL; +} + +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)); + 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); + 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("Failed to make conflict packet: %s", strerror(-r)); + return 0; + } + + r = dns_scope_emit(scope, p); + if (r < 0) + log_debug("Failed to send conflict packet: %s", strerror(-r)); + } + + 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) { + log_debug("Failed to queue conflicting RR: %s", strerror(-r)); + return r; + } + + 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) { + log_debug("Failed to add conflict dispatch event: %s", strerror(-r)); + return r; + } + + 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_C(p) != 0) + return; + + if (DNS_PACKET_T(p) != 0) + return; + + if (manager_our_packet(scope->manager, p)) + return; + + r = dns_packet_extract(p); + if (r < 0) { + log_debug("Failed to extract packet: %s", strerror(-r)); + 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->rrs[i]); + 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->rrs[i], p->family, &p->sender); + if (r <= 0) + continue; + + dns_scope_notify_conflict(scope, p->answer->rrs[i]); + } +} diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h new file mode 100644 index 0000000000..f05648e5a5 --- /dev/null +++ b/src/resolve/resolved-dns-scope.h @@ -0,0 +1,88 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "list.h" + +typedef struct DnsScope DnsScope; + +#include "resolved-dns-server.h" +#include "resolved-dns-packet.h" +#include "resolved-dns-query.h" +#include "resolved-dns-cache.h" +#include "resolved-dns-zone.h" +#include "resolved-dns-stream.h" +#include "resolved-manager.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; + + Link *link; + + char **domains; + + DnsCache cache; + DnsZone zone; + + OrderedHashmap *conflict_queue; + sd_event_source *conflict_event_source; + + RateLimit ratelimit; + + 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); + +int dns_scope_emit(DnsScope *s, DnsPacket *p); +int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *address, uint16_t port); + +DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain); +int dns_scope_good_key(DnsScope *s, DnsResourceKey *key); +int dns_scope_good_dns_server(DnsScope *s, int family, const union in_addr_union *address); + +DnsServer *dns_scope_get_dns_server(DnsScope *s); +void dns_scope_next_dns_server(DnsScope *s); + +int dns_scope_llmnr_membership(DnsScope *s, bool b); + +void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p); + +DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsQuestion *question, bool cache_ok); + +int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr); +void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p); diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c new file mode 100644 index 0000000000..caf06fe450 --- /dev/null +++ b/src/resolve/resolved-dns-server.c @@ -0,0 +1,127 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "siphash24.h" + +#include "resolved-dns-server.h" + +int dns_server_new( + Manager *m, + DnsServer **ret, + DnsServerType type, + Link *l, + int family, + const union in_addr_union *in_addr) { + + DnsServer *s, *tail; + + assert(m); + assert((type == DNS_SERVER_LINK) == !!l); + assert(in_addr); + + s = new0(DnsServer, 1); + if (!s) + return -ENOMEM; + + s->type = type; + s->family = family; + s->address = *in_addr; + + if (type == DNS_SERVER_LINK) { + LIST_FIND_TAIL(servers, l->dns_servers, tail); + LIST_INSERT_AFTER(servers, l->dns_servers, tail, s); + s->link = l; + } else if (type == DNS_SERVER_SYSTEM) { + LIST_FIND_TAIL(servers, m->dns_servers, tail); + LIST_INSERT_AFTER(servers, m->dns_servers, tail, s); + } else if (type == DNS_SERVER_FALLBACK) { + LIST_FIND_TAIL(servers, m->fallback_dns_servers, tail); + LIST_INSERT_AFTER(servers, m->fallback_dns_servers, tail, s); + } else + assert_not_reached("Unknown server type"); + + s->manager = m; + + /* 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_free(DnsServer *s) { + if (!s) + return NULL; + + if (s->manager) { + if (s->type == DNS_SERVER_LINK) + LIST_REMOVE(servers, s->link->dns_servers, s); + else if (s->type == DNS_SERVER_SYSTEM) + LIST_REMOVE(servers, s->manager->dns_servers, s); + else if (s->type == DNS_SERVER_FALLBACK) + LIST_REMOVE(servers, s->manager->fallback_dns_servers, s); + else + assert_not_reached("Unknown server type"); + + if (s->manager->current_dns_server == s) + manager_set_dns_server(s->manager, NULL); + } + + if (s->link && s->link->current_dns_server == s) + link_set_dns_server(s->link, NULL); + + free(s); + + return NULL; +} + +static unsigned long dns_server_hash_func(const void *p, const uint8_t hash_key[HASH_KEY_SIZE]) { + const DnsServer *s = p; + uint64_t u; + + siphash24((uint8_t*) &u, &s->address, FAMILY_ADDRESS_SIZE(s->family), hash_key); + u = u * hash_key[0] + u + s->family; + + return u; +} + +static int dns_server_compare_func(const void *a, const void *b) { + const DnsServer *x = a, *y = b; + + if (x->family < y->family) + return -1; + if (x->family > y->family) + return 1; + + return memcmp(&x->address, &y->address, FAMILY_ADDRESS_SIZE(x->family)); +} + +const struct hash_ops dns_server_hash_ops = { + .hash = dns_server_hash_func, + .compare = dns_server_compare_func +}; diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h new file mode 100644 index 0000000000..a438a27763 --- /dev/null +++ b/src/resolve/resolved-dns-server.h @@ -0,0 +1,63 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "in-addr-util.h" + +typedef struct DnsServer DnsServer; +typedef enum DnsServerSource DnsServerSource; + +typedef enum DnsServerType { + DNS_SERVER_SYSTEM, + DNS_SERVER_FALLBACK, + DNS_SERVER_LINK, +} DnsServerType; + +#include "resolved-manager.h" +#include "resolved-link.h" + +struct DnsServer { + Manager *manager; + + DnsServerType type; + + Link *link; + + int family; + union in_addr_union address; + + bool marked:1; + + LIST_FIELDS(DnsServer, servers); +}; + +int dns_server_new( + Manager *m, + DnsServer **s, + DnsServerType type, + Link *l, + int family, + const union in_addr_union *address); + +DnsServer* dns_server_free(DnsServer *s); + +extern const struct hash_ops dns_server_hash_ops; diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c new file mode 100644 index 0000000000..3690679ec6 --- /dev/null +++ b/src/resolve/resolved-dns-stream.c @@ -0,0 +1,402 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <netinet/tcp.h> + +#include "missing.h" +#include "resolved-dns-stream.h" + +#define DNS_STREAM_TIMEOUT_USEC (10 * USEC_PER_SEC) +#define DNS_STREAMS_MAX 128 + +static void dns_stream_stop(DnsStream *s) { + assert(s); + + s->io_event_source = sd_event_source_unref(s->io_event_source); + s->timeout_event_source = sd_event_source_unref(s->timeout_event_source); + s->fd = safe_close(s->fd); +} + +static int dns_stream_update_io(DnsStream *s) { + int f = 0; + + assert(s); + + if (s->write_packet && s->n_written < sizeof(s->write_size) + s->write_packet->size) + f |= EPOLLOUT; + if (!s->read_packet || s->n_read < sizeof(s->read_size) + s->read_packet->size) + f |= EPOLLIN; + + return sd_event_source_set_io_events(s->io_event_source, f); +} + +static int dns_stream_complete(DnsStream *s, int error) { + assert(s); + + dns_stream_stop(s); + + if (s->complete) + s->complete(s, error); + else + dns_stream_free(s); + + return 0; +} + +static int 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; + for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg)) { + + if (cmsg->cmsg_level == IPPROTO_IPV6) { + assert(s->peer.sa.sa_family == AF_INET6); + + switch (cmsg->cmsg_type) { + + case IPV6_PKTINFO: { + struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg); + + if (s->ifindex <= 0) + s->ifindex = i->ipi6_ifindex; + break; + } + + case IPV6_HOPLIMIT: + s->ttl = *(int *) CMSG_DATA(cmsg); + break; + } + + } else if (cmsg->cmsg_level == IPPROTO_IP) { + assert(s->peer.sa.sa_family == AF_INET); + + switch (cmsg->cmsg_type) { + + case IP_PKTINFO: { + struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg); + + if (s->ifindex <= 0) + s->ifindex = i->ipi_ifindex; + break; + } + + case IP_TTL: + s->ttl = *(int *) CMSG_DATA(cmsg); + break; + } + } + } + + /* The Linux kernel sets the interface index to the loopback + * device if the connection came from the local host since it + * avoids the routing table in such a case. Let's unset the + * interface index in such a case. */ + if (s->ifindex == 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("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("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_free(DnsStream *s) { + if (!s) + return NULL; + + dns_stream_stop(s); + + if (s->manager) { + LIST_REMOVE(streams, s->manager->dns_streams, s); + s->manager->n_dns_streams--; + } + + dns_packet_unref(s->write_packet); + dns_packet_unref(s->read_packet); + + free(s); + + return 0; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_free); + +int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { + static const int one = 1; + _cleanup_(dns_stream_freep) 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->fd = -1; + s->protocol = protocol; + + r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); + if (r < 0) + return -errno; + + r = sd_event_add_io(m->event, &s->io_event_source, fd, EPOLLIN, on_stream_io, s); + if (r < 0) + return r; + + 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; + + 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 new file mode 100644 index 0000000000..46eae31c60 --- /dev/null +++ b/src/resolve/resolved-dns-stream.h @@ -0,0 +1,64 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "socket-util.h" + +typedef struct DnsStream DnsStream; + +#include "resolved-dns-packet.h" +#include "resolved-dns-transaction.h" +#include "resolved-manager.h" + +struct DnsStream { + Manager *manager; + + DnsProtocol protocol; + + int fd; + union sockaddr_union peer; + socklen_t peer_salen; + union sockaddr_union local; + socklen_t local_salen; + int ifindex; + uint32_t ttl; + 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; + + LIST_FIELDS(DnsStream, streams); +}; + +int dns_stream_new(Manager *m, DnsStream **s, DnsProtocol protocol, int fd); +DnsStream *dns_stream_free(DnsStream *s); + +int dns_stream_write_packet(DnsStream *s, DnsPacket *p); diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c new file mode 100644 index 0000000000..74b0634142 --- /dev/null +++ b/src/resolve/resolved-dns-transaction.c @@ -0,0 +1,619 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "af-list.h" + +#include "resolved-dns-transaction.h" + +DnsTransaction* dns_transaction_free(DnsTransaction *t) { + DnsQuery *q; + DnsZoneItem *i; + + if (!t) + return NULL; + + sd_event_source_unref(t->timeout_event_source); + + dns_question_unref(t->question); + dns_packet_unref(t->sent); + dns_packet_unref(t->received); + dns_answer_unref(t->cached); + + dns_stream_free(t->stream); + + if (t->scope) { + 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 ((q = set_steal_first(t->queries))) + set_remove(q->transactions, t); + set_free(t->queries); + + while ((i = set_steal_first(t->zone_items))) + i->probe_transaction = NULL; + set_free(t->zone_items); + + free(t); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsTransaction*, dns_transaction_free); + +void dns_transaction_gc(DnsTransaction *t) { + assert(t); + + if (t->block_gc > 0) + return; + + if (set_isempty(t->queries) && set_isempty(t->zone_items)) + dns_transaction_free(t); +} + +int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsQuestion *q) { + _cleanup_(dns_transaction_freep) DnsTransaction *t = NULL; + int r; + + assert(ret); + assert(s); + assert(q); + + r = hashmap_ensure_allocated(&s->manager->dns_transactions, NULL); + if (r < 0) + return r; + + t = new0(DnsTransaction, 1); + if (!t) + return -ENOMEM; + + t->question = dns_question_ref(q); + + do + random_bytes(&t->id, sizeof(t->id)); + while (t->id == 0 || + hashmap_get(s->manager->dns_transactions, UINT_TO_PTR(t->id))); + + r = hashmap_put(s->manager->dns_transactions, UINT_TO_PTR(t->id), t); + if (r < 0) { + t->id = 0; + return r; + } + + LIST_PREPEND(transactions_by_scope, s->transactions, t); + t->scope = s; + + if (ret) + *ret = t; + + t = NULL; + + return 0; +} + +static void dns_transaction_stop(DnsTransaction *t) { + assert(t); + + t->timeout_event_source = sd_event_source_unref(t->timeout_event_source); + t->stream = dns_stream_free(t->stream); +} + +static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) { + _cleanup_free_ char *pretty = NULL; + DnsZoneItem *z; + + assert(t); + assert(p); + + if (manager_our_packet(t->scope->manager, p) != 0) + return; + + in_addr_to_string(p->family, &p->sender, &pretty); + + log_debug("Transaction on scope %s on %s/%s got tentative packet from %s", + dns_protocol_to_string(t->scope->protocol), + t->scope->link ? t->scope->link->name : "*", + t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family), + 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->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) { + DnsQuery *q; + DnsZoneItem *z; + Iterator i; + + assert(t); + assert(!IN_SET(state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING)); + + if (!IN_SET(t->state, DNS_TRANSACTION_NULL, 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("Transaction on scope %s on %s/%s now complete with <%s>", + dns_protocol_to_string(t->scope->protocol), + t->scope->link ? t->scope->link->name : "*", + t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family), + dns_transaction_state_to_string(state)); + + t->state = state; + + dns_transaction_stop(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(q, t->queries, i) + dns_query_ready(q); + SET_FOREACH(z, t->zone_items, i) + dns_zone_item_ready(z); + t->block_gc--; + + dns_transaction_gc(t); +} + +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); + + t->stream = dns_stream_free(t->stream); + + if (error != 0) { + dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + return 0; + } + + if (dns_packet_validate_reply(p) <= 0) { + log_debug("Invalid LLMNR TCP 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 */ + if (t->state == DNS_TRANSACTION_PENDING) + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + + return 0; +} + +static int dns_transaction_open_tcp(DnsTransaction *t) { + _cleanup_close_ int fd = -1; + int r; + + assert(t); + + if (t->stream) + return 0; + + if (t->scope->protocol == DNS_PROTOCOL_DNS) + fd = dns_scope_tcp_socket(t->scope, AF_UNSPEC, NULL, 53); + else if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { + + /* When we already received a query to this (but it was truncated), send to its sender address */ + if (t->received) + fd = dns_scope_tcp_socket(t->scope, t->received->family, &t->received->sender, t->received->sender_port); + else { + union in_addr_union address; + int family; + + /* Otherwise, try to talk to the owner of a + * the IP address, in case this is a reverse + * PTR lookup */ + r = dns_question_extract_reverse_address(t->question, &family, &address); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + fd = dns_scope_tcp_socket(t->scope, family, &address, 5355); + } + } else + 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_free(t->stream); + return r; + } + + t->received = dns_packet_unref(t->received); + 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 */ + if (t->scope->link) + t->stream->ifindex = t->scope->link->ifindex; + + return 0; +} + +void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { + int r; + + assert(t); + assert(p); + assert(t->state == DNS_TRANSACTION_PENDING); + + /* Note that this call might invalidate the query. Callers + * should hence not attempt to access the query or transaction + * after calling this function. */ + + if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { + assert(t->scope->link); + + /* For LLMNR we will not accept any packets from other + * interfaces */ + + if (p->ifindex != t->scope->link->ifindex) + return; + + if (p->family != t->scope->family) + return; + + /* Tentative packets are not full responses but still + * useful for identifying uniqueness conflicts during + * probing. */ + if (DNS_PACKET_T(p)) { + dns_transaction_tentative(t, p); + return; + } + } + + if (t->scope->protocol == DNS_PROTOCOL_DNS) { + + /* For DNS we are fine with accepting packets on any + * interface, but the source IP address must be one of + * a valid DNS server */ + + if (!dns_scope_good_dns_server(t->scope, p->family, &p->sender)) + return; + + if (p->sender_port != 53) + return; + } + + if (t->received != p) { + dns_packet_unref(t->received); + t->received = dns_packet_ref(p); + } + + if (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; + } + } + + if (DNS_PACKET_TC(p)) { + /* 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 < 0) { + /* On LLMNR, if we cannot connect to the host, + * we immediately give up */ + if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { + dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + return; + } + + /* On DNS, couldn't send? Try immediately again, with a new server */ + dns_scope_next_dns_server(t->scope); + + r = dns_transaction_go(t); + if (r < 0) { + dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + return; + } + + return; + } + } + + /* Parse and update the cache */ + r = dns_packet_extract(p); + if (r < 0) { + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + + /* According to RFC 4795, section 2.9. only the RRs from the answer section shall be cached */ + dns_cache_put(&t->scope->cache, p->question, DNS_PACKET_RCODE(p), p->answer, DNS_PACKET_ANCOUNT(p), 0, p->family, &p->sender); + + if (DNS_PACKET_RCODE(p) == DNS_RCODE_SUCCESS) + dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); + else + dns_transaction_complete(t, DNS_TRANSACTION_FAILURE); +} + +static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdata) { + DnsTransaction *t = userdata; + int r; + + assert(s); + assert(t); + + /* Timeout reached? Try again, with a new server */ + dns_scope_next_dns_server(t->scope); + + r = dns_transaction_go(t); + if (r < 0) + dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + + return 0; +} + +static int dns_transaction_make_packet(DnsTransaction *t) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + unsigned n, added = 0; + int r; + + assert(t); + + if (t->sent) + return 0; + + r = dns_packet_new_query(&p, t->scope->protocol, 0); + if (r < 0) + return r; + + for (n = 0; n < t->question->n_keys; n++) { + r = dns_scope_good_key(t->scope, t->question->keys[n]); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dns_packet_append_key(p, t->question->keys[n], NULL); + if (r < 0) + return r; + + added++; + } + + if (added <= 0) + return -EDOM; + + DNS_PACKET_HEADER(p)->qdcount = htobe16(added); + DNS_PACKET_HEADER(p)->id = t->id; + + t->sent = p; + p = NULL; + + return 0; +} + +int dns_transaction_go(DnsTransaction *t) { + bool had_stream; + int r; + + assert(t); + + had_stream = !!t->stream; + + dns_transaction_stop(t); + + log_debug("Excercising transaction on scope %s on %s/%s", + dns_protocol_to_string(t->scope->protocol), + t->scope->link ? t->scope->link->name : "*", + t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family)); + + if (t->n_attempts >= TRANSACTION_ATTEMPTS_MAX(t->scope->protocol)) { + dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); + return 0; + } + + if (t->scope->protocol == DNS_PROTOCOL_LLMNR && had_stream) { + /* If 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->received = dns_packet_unref(t->received); + t->cached = dns_answer_unref(t->cached); + t->cached_rcode = 0; + + /* Check the cache, but only if this transaction is not used + * for probing or verifying a zone item. */ + if (set_isempty(t->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->question, &t->cached_rcode, &t->cached); + if (r < 0) + return r; + if (r > 0) { + log_debug("Cache hit!"); + if (t->cached_rcode == DNS_RCODE_SUCCESS) + dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); + else + dns_transaction_complete(t, DNS_TRANSACTION_FAILURE); + return 0; + } + } + + if (t->scope->protocol == DNS_PROTOCOL_LLMNR && !t->initial_jitter) { + usec_t jitter; + + /* RFC 4795 Section 2.7 suggests all queries should be + * delayed by a random time from 0 to JITTER_INTERVAL. */ + + t->initial_jitter = true; + + random_bytes(&jitter, sizeof(jitter)); + jitter %= LLMNR_JITTER_INTERVAL_USEC; + + r = sd_event_add_time( + t->scope->manager->event, + &t->timeout_event_source, + clock_boottime_or_monotonic(), + now(clock_boottime_or_monotonic()) + jitter, + LLMNR_JITTER_INTERVAL_USEC, + on_transaction_timeout, t); + if (r < 0) + return r; + + t->n_attempts = 0; + t->state = DNS_TRANSACTION_PENDING; + + log_debug("Delaying LLMNR transaction for " USEC_FMT "us.", jitter); + return 0; + } + + log_debug("Cache miss!"); + + /* Otherwise, we need to ask the network */ + r = dns_transaction_make_packet(t); + if (r == -EDOM) { + /* Not the right request to make on this network? + * (i.e. an A request made on IPv6 or an AAAA request + * made on IPv4, on LLMNR or mDNS.) */ + dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); + return 0; + } + if (r < 0) + return r; + + if (t->scope->protocol == DNS_PROTOCOL_LLMNR && + (dns_question_endswith(t->question, "in-addr.arpa") > 0 || + dns_question_endswith(t->question, "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 try via TCP */ + r = dns_scope_emit(t->scope, t->sent); + if (r == -EMSGSIZE) + 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 < 0) { + if (t->scope->protocol != DNS_PROTOCOL_DNS) { + dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + return 0; + } + + /* Couldn't send? Try immediately again, with a new server */ + dns_scope_next_dns_server(t->scope); + + return dns_transaction_go(t); + } + + r = sd_event_add_time( + t->scope->manager->event, + &t->timeout_event_source, + clock_boottime_or_monotonic(), + now(clock_boottime_or_monotonic()) + TRANSACTION_TIMEOUT_USEC(t->scope->protocol), 0, + on_transaction_timeout, t); + if (r < 0) + return r; + + t->state = DNS_TRANSACTION_PENDING; + return 1; +} + +static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] = { + [DNS_TRANSACTION_NULL] = "null", + [DNS_TRANSACTION_PENDING] = "pending", + [DNS_TRANSACTION_FAILURE] = "failure", + [DNS_TRANSACTION_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_RESOURCES] = "resources", + [DNS_TRANSACTION_ABORTED] = "aborted", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_transaction_state, DnsTransactionState); diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h new file mode 100644 index 0000000000..182fb7714c --- /dev/null +++ b/src/resolve/resolved-dns-transaction.h @@ -0,0 +1,110 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +typedef struct DnsTransaction DnsTransaction; +typedef enum DnsTransactionState DnsTransactionState; + +enum DnsTransactionState { + DNS_TRANSACTION_NULL, + DNS_TRANSACTION_PENDING, + DNS_TRANSACTION_FAILURE, + DNS_TRANSACTION_SUCCESS, + DNS_TRANSACTION_NO_SERVERS, + DNS_TRANSACTION_TIMEOUT, + DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, + DNS_TRANSACTION_INVALID_REPLY, + DNS_TRANSACTION_RESOURCES, + DNS_TRANSACTION_ABORTED, + _DNS_TRANSACTION_STATE_MAX, + _DNS_TRANSACTION_STATE_INVALID = -1 +}; + +#include "resolved-dns-scope.h" +#include "resolved-dns-rr.h" +#include "resolved-dns-packet.h" +#include "resolved-dns-question.h" +#include "resolved-dns-answer.h" +#include "resolved-dns-stream.h" + +struct DnsTransaction { + DnsScope *scope; + + DnsQuestion *question; + + DnsTransactionState state; + uint16_t id; + + bool initial_jitter; + + DnsPacket *sent, *received; + DnsAnswer *cached; + int cached_rcode; + + sd_event_source *timeout_event_source; + unsigned n_attempts; + + /* TCP connection logic, if we need it */ + DnsStream *stream; + + /* Queries this transaction is referenced by and that shall be + * notified about this specific transaction completing. */ + Set *queries; + + /* Zone items this transaction is referenced by and that shall + * be notified about completion. */ + Set *zone_items; + + unsigned block_gc; + + LIST_FIELDS(DnsTransaction, transactions_by_scope); +}; + +int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsQuestion *q); +DnsTransaction* dns_transaction_free(DnsTransaction *t); + +void 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); + +const char* dns_transaction_state_to_string(DnsTransactionState p) _const_; +DnsTransactionState dns_transaction_state_from_string(const char *s) _pure_; + +/* After how much time to repeat classic DNS requests */ +#define DNS_TRANSACTION_TIMEOUT_USEC (5 * USEC_PER_SEC) + +/* After how much time to repeat LLMNR requests, see RFC 4795 Section 7 */ +#define LLMNR_TRANSACTION_TIMEOUT_USEC (1 * USEC_PER_SEC) + +/* LLMNR Jitter interval, see RFC 4795 Section 7 */ +#define LLMNR_JITTER_INTERVAL_USEC (100 * USEC_PER_MSEC) + +/* Maximum attempts to send DNS requests, across all DNS servers */ +#define DNS_TRANSACTION_ATTEMPTS_MAX 8 + +/* Maximum attempts to send LLMNR requests, see RFC 4795 Section 2.7 */ +#define LLMNR_TRANSACTION_ATTEMPTS_MAX 3 + +#define TRANSACTION_TIMEOUT_USEC(p) (p == DNS_PROTOCOL_LLMNR ? LLMNR_TRANSACTION_TIMEOUT_USEC : DNS_TRANSACTION_TIMEOUT_USEC) +#define TRANSACTION_ATTEMPTS_MAX(p) (p == DNS_PROTOCOL_LLMNR ? LLMNR_TRANSACTION_ATTEMPTS_MAX : DNS_TRANSACTION_ATTEMPTS_MAX) diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c new file mode 100644 index 0000000000..8098ff5e70 --- /dev/null +++ b/src/resolve/resolved-dns-zone.c @@ -0,0 +1,648 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "list.h" + +#include "resolved-dns-zone.h" +#include "resolved-dns-domain.h" +#include "resolved-dns-packet.h" + +/* Never allow more than 1K entries */ +#define ZONE_MAX 1024 + +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->zone_items, 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); + + hashmap_free(z->by_key); + z->by_key = NULL; + + hashmap_free(z->by_name); + z->by_name = NULL; +} + +static DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) { + DnsZoneItem *i; + + assert(z); + assert(rr); + + LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key)) + if (dns_resource_record_equal(i->rr, rr) > 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) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + DnsTransaction *t; + int r; + + assert(i); + + if (i->probe_transaction) + return 0; + + key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key)); + if (!key) + return -ENOMEM; + + question = dns_question_new(1); + if (!question) + return -ENOMEM; + + r = dns_question_add(question, key); + if (r < 0) + return r; + + t = dns_scope_find_transaction(i->scope, question, false); + if (!t) { + r = dns_transaction_new(&t, i->scope, question); + if (r < 0) + return r; + } + + r = set_ensure_allocated(&t->zone_items, NULL); + if (r < 0) + goto gc; + + r = set_put(t->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_ready(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 (rr->key->class == DNS_CLASS_ANY) + return -EINVAL; + if (rr->key->type == DNS_TYPE_ANY) + 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, DnsQuestion *q, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL; + unsigned i, n_answer = 0, n_soa = 0; + bool tentative = true; + int r; + + assert(z); + assert(q); + assert(ret_answer); + assert(ret_soa); + + if (q->n_keys <= 0) { + *ret_answer = NULL; + *ret_soa = NULL; + + if (ret_tentative) + *ret_tentative = false; + + return 0; + } + + /* First iteration, count what we have */ + for (i = 0; i < q->n_keys; i++) { + DnsZoneItem *j, *first; + + if (q->keys[i]->type == DNS_TYPE_ANY || + q->keys[i]->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(q->keys[i])); + 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(q->keys[i], j->rr); + if (k < 0) + return k; + if (k > 0) { + n_answer++; + added = true; + } + + } + + if (found && !added) + n_soa++; + + } else { + bool found = false; + + /* If this is a specific match, then look for + * the right key immediately */ + + first = hashmap_get(z->by_key, q->keys[i]); + 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(q->keys[i])); + LIST_FOREACH(by_name, j, first) { + if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) + continue; + + n_soa++; + break; + } + } + } + } + + if (n_answer <= 0 && n_soa <= 0) { + *ret_answer = NULL; + *ret_soa = NULL; + + if (ret_tentative) + *ret_tentative = false; + + return 0; + } + + if (n_answer > 0) { + answer = dns_answer_new(n_answer); + if (!answer) + return -ENOMEM; + } + + if (n_soa > 0) { + soa = dns_answer_new(n_soa); + if (!soa) + return -ENOMEM; + } + + /* Second iteration, actually add the RRs to the answers */ + for (i = 0; i < q->n_keys; i++) { + DnsZoneItem *j, *first; + + if (q->keys[i]->type == DNS_TYPE_ANY || + q->keys[i]->class == DNS_CLASS_ANY) { + bool found = false, added = false; + int k; + + first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i])); + 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(q->keys[i], j->rr); + if (k < 0) + return k; + if (k > 0) { + r = dns_answer_add(answer, j->rr); + if (r < 0) + return r; + + added = true; + } + } + + if (found && !added) { + r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(q->keys[i]), LLMNR_DEFAULT_TTL); + if (r < 0) + return r; + } + } else { + bool found = false; + + first = hashmap_get(z->by_key, q->keys[i]); + 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); + if (r < 0) + return r; + } + + if (!found) { + bool add_soa = false; + + first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i])); + 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(q->keys[i]), LLMNR_DEFAULT_TTL); + if (r < 0) + return r; + } + } + } + } + + *ret_answer = answer; + answer = NULL; + + *ret_soa = soa; + soa = NULL; + + if (ret_tentative) + *ret_tentative = tentative; + + return 1; +} + +void dns_zone_item_conflict(DnsZoneItem *i) { + _cleanup_free_ char *pretty = NULL; + + assert(i); + + if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED)) + return; + + dns_resource_record_to_string(i->rr, &pretty); + log_info("Detected conflict on %s", strna(pretty)); + + dns_zone_item_probe_stop(i); + + /* Withdraw the conflict item */ + i->state = DNS_ZONE_ITEM_WITHDRAWN; + + /* Maybe change the hostname */ + if (dns_name_equal(i->scope->manager->hostname, DNS_RESOURCE_KEY_NAME(i->rr->key)) > 0) + manager_next_hostname(i->scope->manager); +} + +void dns_zone_item_ready(DnsZoneItem *i) { + _cleanup_free_ char *pretty = NULL; + + 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)) + 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."); + } + + dns_resource_record_to_string(i->rr, &pretty); + log_debug("Record %s successfully probed.", strna(pretty)); + + dns_zone_item_probe_stop(i); + i->state = DNS_ZONE_ITEM_ESTABLISHED; +} + +static int dns_zone_item_verify(DnsZoneItem *i) { + _cleanup_free_ char *pretty = NULL; + int r; + + assert(i); + + if (i->state != DNS_ZONE_ITEM_ESTABLISHED) + return 0; + + dns_resource_record_to_string(i->rr, &pretty); + log_debug("Verifying RR %s", strna(pretty)); + + i->state = DNS_ZONE_ITEM_VERIFYING; + r = dns_zone_item_probe_start(i); + if (r < 0) { + log_error("Failed to start probing for verifying RR: %s", strerror(-r)); + 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); + } +} diff --git a/src/resolve/resolved-dns-zone.h b/src/resolve/resolved-dns-zone.h new file mode 100644 index 0000000000..71851265c6 --- /dev/null +++ b/src/resolve/resolved-dns-zone.h @@ -0,0 +1,80 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "hashmap.h" + +typedef struct DnsZone { + Hashmap *by_key; + Hashmap *by_name; +} DnsZone; + +typedef struct DnsZoneItem DnsZoneItem; +typedef enum DnsZoneItemState DnsZoneItemState; + +#include "resolved-dns-rr.h" +#include "resolved-dns-question.h" +#include "resolved-dns-answer.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, DnsQuestion *q, DnsAnswer **answer, DnsAnswer **soa, bool *tentative); + +void dns_zone_item_conflict(DnsZoneItem *i); +void dns_zone_item_ready(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); diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf index 71e998051a..8e78fbf06a 100644 --- a/src/resolve/resolved-gperf.gperf +++ b/src/resolve/resolved-gperf.gperf @@ -1,7 +1,7 @@ %{ #include <stddef.h> #include "conf-parser.h" -#include "resolved.h" +#include "resolved-conf.h" %} struct ConfigPerfItem; %null_strings @@ -14,4 +14,6 @@ struct ConfigPerfItem; %struct-type %includes %% -Resolve.DNS, config_parse_dnsv, 0, offsetof(Manager, fallback_dns) +Resolve.DNS, config_parse_dnsv, DNS_SERVER_SYSTEM, 0 +Resolve.FallbackDNS, config_parse_dnsv, DNS_SERVER_FALLBACK, 0 +Resolve.LLMNR, config_parse_support, 0, offsetof(Manager, llmnr_support) diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c new file mode 100644 index 0000000000..4def672214 --- /dev/null +++ b/src/resolve/resolved-link.c @@ -0,0 +1,552 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <net/if.h> + +#include "sd-network.h" +#include "strv.h" +#include "missing.h" +#include "resolved-link.h" + +int link_new(Manager *m, Link **ret, int ifindex) { + _cleanup_(link_freep) Link *l = NULL; + int r; + + assert(m); + assert(ifindex > 0); + + r = hashmap_ensure_allocated(&m->links, NULL); + if (r < 0) + return r; + + l = new0(Link, 1); + if (!l) + return -ENOMEM; + + l->ifindex = ifindex; + l->llmnr_support = SUPPORT_YES; + + r = hashmap_put(m->links, INT_TO_PTR(ifindex), l); + if (r < 0) + return r; + + l->manager = m; + + if (ret) + *ret = l; + l = NULL; + + return 0; +} + +Link *link_free(Link *l) { + + if (!l) + return NULL; + + while (l->addresses) + link_address_free(l->addresses); + + if (l->manager) + hashmap_remove(l->manager->links, INT_TO_PTR(l->ifindex)); + + dns_scope_free(l->unicast_scope); + dns_scope_free(l->llmnr_ipv4_scope); + dns_scope_free(l->llmnr_ipv6_scope); + + while (l->dns_servers) + dns_server_free(l->dns_servers); + + free(l); + return NULL; +} + +static void link_allocate_scopes(Link *l) { + int r; + + assert(l); + + if (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("Failed to allocate DNS scope: %s", strerror(-r)); + } + } else + l->unicast_scope = dns_scope_free(l->unicast_scope); + + if (link_relevant(l, AF_INET) && + l->llmnr_support != SUPPORT_NO && + l->manager->llmnr_support != SUPPORT_NO) { + 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("Failed to allocate LLMNR IPv4 scope: %s", strerror(-r)); + } + } else + l->llmnr_ipv4_scope = dns_scope_free(l->llmnr_ipv4_scope); + + if (link_relevant(l, AF_INET6) && + l->llmnr_support != SUPPORT_NO && + l->manager->llmnr_support != SUPPORT_NO && + 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("Failed to allocate LLMNR IPv6 scope: %s", strerror(-r)); + } + } else + l->llmnr_ipv6_scope = dns_scope_free(l->llmnr_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_update_rtnl(Link *l, sd_rtnl_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; + + sd_rtnl_message_read_u32(m, IFLA_MTU, &l->mtu); + + if (sd_rtnl_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_servers(Link *l) { + _cleanup_strv_free_ char **nameservers = NULL; + char **nameserver; + DnsServer *s, *nx; + int r; + + assert(l); + + r = sd_network_link_get_dns(l->ifindex, &nameservers); + if (r < 0) + goto clear; + + LIST_FOREACH(servers, s, l->dns_servers) + s->marked = true; + + STRV_FOREACH(nameserver, nameservers) { + union in_addr_union a; + int family; + + r = in_addr_from_string_auto(*nameserver, &family, &a); + if (r < 0) + goto clear; + + s = link_find_dns_server(l, family, &a); + if (s) + s->marked = false; + else { + r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, family, &a); + if (r < 0) + goto clear; + } + } + + LIST_FOREACH_SAFE(servers, s, nx, l->dns_servers) + if (s->marked) + dns_server_free(s); + + return 0; + +clear: + while (l->dns_servers) + dns_server_free(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 < 0) + goto clear; + + r = parse_boolean(b); + if (r < 0) { + if (streq(b, "resolve")) + l->llmnr_support = SUPPORT_RESOLVE; + else + goto clear; + + } else if (r > 0) + l->llmnr_support = SUPPORT_YES; + else + l->llmnr_support = SUPPORT_NO; + + return 0; + +clear: + l->llmnr_support = SUPPORT_YES; + return r; +} + +static int link_update_domains(Link *l) { + int r; + + if (!l->unicast_scope) + return 0; + + strv_free(l->unicast_scope->domains); + l->unicast_scope->domains = NULL; + + r = sd_network_link_get_domains(l->ifindex, + &l->unicast_scope->domains); + if (r < 0) + return r; + + return 0; +} + +int link_update_monitor(Link *l) { + assert(l); + + link_update_dns_servers(l); + link_update_llmnr_support(l); + link_allocate_scopes(l); + link_update_domains(l); + link_add_rrs(l, false); + + return 0; +} + +bool link_relevant(Link *l, int family) { + _cleanup_free_ char *state = NULL; + LinkAddress *a; + + assert(l); + + /* A link is relevant if it isn't a loopback or pointopoint + * device, has a link beat, can do multicast and has at least + * one relevant IP address */ + + if (l->flags & (IFF_LOOPBACK|IFF_POINTOPOINT|IFF_DORMANT)) + return false; + + if ((l->flags & (IFF_UP|IFF_LOWER_UP|IFF_MULTICAST)) != (IFF_UP|IFF_LOWER_UP|IFF_MULTICAST)) + return false; + + 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 (a->family == family && link_address_relevant(a)) + 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_find_dns_server(Link *l, int family, const union in_addr_union *in_addr) { + DnsServer *s; + + assert(l); + + LIST_FOREACH(servers, s, l->dns_servers) + if (s->family == family && in_addr_equal(family, &s->address, in_addr)) + return s; + return NULL; +} + +DnsServer* link_set_dns_server(Link *l, DnsServer *s) { + assert(l); + + if (l->current_dns_server == s) + return s; + + if (s) { + _cleanup_free_ char *ip = NULL; + + in_addr_to_string(s->family, &s->address, &ip); + log_info("Switching to DNS server %s for interface %s.", strna(ip), l->name); + } + + l->current_dns_server = 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; + + if (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); +} + +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); + + free(a); + return NULL; +} + +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) && + a->link->llmnr_ipv4_scope && + a->link->llmnr_support == SUPPORT_YES && + a->link->manager->llmnr_support == SUPPORT_YES) { + + if (!a->link->manager->host_ipv4_key) { + a->link->manager->host_ipv4_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, a->link->manager->hostname); + if (!a->link->manager->host_ipv4_key) { + r = -ENOMEM; + goto fail; + } + } + + if (!a->llmnr_address_rr) { + a->llmnr_address_rr = dns_resource_record_new(a->link->manager->host_ipv4_key); + if (!a->llmnr_address_rr) { + r = -ENOMEM; + goto fail; + } + + a->llmnr_address_rr->a.in_addr = a->in_addr.in; + a->llmnr_address_rr->ttl = 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->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("Failed to add A record to LLMNR zone: %s", strerror(-r)); + + 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("Failed to add IPv6 PTR record to LLMNR zone: %s", strerror(-r)); + } 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) && + a->link->llmnr_ipv6_scope && + a->link->llmnr_support == SUPPORT_YES && + a->link->manager->llmnr_support == SUPPORT_YES) { + + if (!a->link->manager->host_ipv6_key) { + a->link->manager->host_ipv6_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, a->link->manager->hostname); + if (!a->link->manager->host_ipv6_key) { + r = -ENOMEM; + goto fail; + } + } + + if (!a->llmnr_address_rr) { + a->llmnr_address_rr = dns_resource_record_new(a->link->manager->host_ipv6_key); + if (!a->llmnr_address_rr) { + r = -ENOMEM; + goto fail; + } + + a->llmnr_address_rr->aaaa.in6_addr = a->in_addr.in6; + a->llmnr_address_rr->ttl = 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->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("Failed to add AAAA record to LLMNR zone: %s", strerror(-r)); + + 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("Failed to add IPv6 PTR record to LLMNR zone: %s", strerror(-r)); + } 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("Failed to update address RRs: %s", strerror(-r)); +} + +int link_address_update_rtnl(LinkAddress *a, sd_rtnl_message *m) { + int r; + assert(a); + assert(m); + + r = sd_rtnl_message_addr_get_flags(m, &a->flags); + if (r < 0) + return r; + + 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) { + assert(a); + + if (a->flags & (IFA_F_DEPRECATED|IFA_F_TENTATIVE)) + return false; + + if (IN_SET(a->scope, RT_SCOPE_HOST, RT_SCOPE_NOWHERE)) + return false; + + return true; +} diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h new file mode 100644 index 0000000000..4f0702e872 --- /dev/null +++ b/src/resolve/resolved-link.h @@ -0,0 +1,91 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <net/if.h> + +#include "in-addr-util.h" +#include "ratelimit.h" + +typedef struct Link Link; +typedef struct LinkAddress LinkAddress; + +#include "resolved-dns-server.h" +#include "resolved-dns-scope.h" +#include "resolved-dns-rr.h" +#include "resolved-manager.h" + +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; + + Support llmnr_support; + + DnsScope *unicast_scope; + DnsScope *llmnr_ipv4_scope; + DnsScope *llmnr_ipv6_scope; + + char name[IF_NAMESIZE]; + uint32_t mtu; +}; + +int link_new(Manager *m, Link **ret, int ifindex); +Link *link_free(Link *l); +int link_update_rtnl(Link *l, sd_rtnl_message *m); +int link_update_monitor(Link *l); +bool link_relevant(Link *l, int family); +LinkAddress* link_find_address(Link *l, int family, const union in_addr_union *in_addr); +void link_add_rrs(Link *l, bool force_remove); + +DnsServer* link_set_dns_server(Link *l, DnsServer *s); +DnsServer* link_find_dns_server(Link *l, int family, const union in_addr_union *in_addr); +DnsServer* link_get_dns_server(Link *l); +void link_next_dns_server(Link *l); + +int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr); +LinkAddress *link_address_free(LinkAddress *a); +int link_address_update_rtnl(LinkAddress *a, sd_rtnl_message *m); +bool link_address_relevant(LinkAddress *l); +void link_address_add_rrs(LinkAddress *a, bool force_remove); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_free); diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 3ed0603f9b..c4a5b08773 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -21,118 +21,461 @@ #include <arpa/inet.h> #include <resolv.h> -#include <linux/if.h> +#include <net/if.h> +#include <sys/ioctl.h> +#include <sys/poll.h> +#include <netinet/in.h> -#include "resolved.h" +#include "rtnl-util.h" #include "event-util.h" #include "network-util.h" -#include "sd-dhcp-lease.h" -#include "dhcp-lease-internal.h" #include "network-internal.h" #include "conf-parser.h" +#include "socket-util.h" +#include "af-list.h" +#include "utf8.h" +#include "fileio-label.h" -static int set_fallback_dns(Manager *m, const char *string) { - char *word, *state; - size_t length; - int r; +#include "resolved-dns-domain.h" +#include "resolved-conf.h" +#include "resolved-bus.h" +#include "resolved-manager.h" + +#define SEND_TIMEOUT_USEC (200 * USEC_PER_MSEC) + +static int manager_process_link(sd_rtnl *rtnl, sd_rtnl_message *mm, void *userdata) { + Manager *m = userdata; + uint16_t type; + Link *l; + int ifindex, r; + + assert(rtnl); + assert(m); + assert(mm); + + r = sd_rtnl_message_get_type(mm, &type); + if (r < 0) + goto fail; + + r = sd_rtnl_message_link_get_ifindex(mm, &ifindex); + if (r < 0) + goto fail; + + l = hashmap_get(m->links, INT_TO_PTR(ifindex)); + + switch (type) { + + case RTM_NEWLINK:{ + bool is_new = !l; + + if (!l) { + r = link_new(m, &l, ifindex); + if (r < 0) + goto fail; + } + + r = link_update_rtnl(l, mm); + if (r < 0) + goto fail; + + r = link_update_monitor(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_free(l); + } + + break; + } + + return 0; + +fail: + log_warning("Failed to process RTNL link message: %s", strerror(-r)); + return 0; +} +static int manager_process_address(sd_rtnl *rtnl, sd_rtnl_message *mm, void *userdata) { + Manager *m = userdata; + union in_addr_union address; + uint16_t type; + int r, ifindex, family; + LinkAddress *a; + Link *l; + + assert(rtnl); + assert(mm); assert(m); - assert(string); - FOREACH_WORD_QUOTED(word, length, string, state) { - _cleanup_free_ Address *address = NULL; - Address *tail; - _cleanup_free_ char *addrstr = NULL; + r = sd_rtnl_message_get_type(mm, &type); + if (r < 0) + goto fail; + + r = sd_rtnl_message_addr_get_ifindex(mm, &ifindex); + if (r < 0) + goto fail; + + l = hashmap_get(m->links, INT_TO_PTR(ifindex)); + if (!l) + return 0; - address = new0(Address, 1); - if (!address) - return -ENOMEM; + r = sd_rtnl_message_addr_get_family(mm, &family); + if (r < 0) + goto fail; - addrstr = strndup(word, length); - if (!addrstr) - return -ENOMEM; + switch (family) { - r = net_parse_inaddr(addrstr, &address->family, &address->in_addr); + case AF_INET: + r = sd_rtnl_message_read_in_addr(mm, IFA_LOCAL, &address.in); if (r < 0) { - log_debug("Ignoring invalid DNS address '%s'", addrstr); - continue; + r = sd_rtnl_message_read_in_addr(mm, IFA_ADDRESS, &address.in); + if (r < 0) + goto fail; + } + + break; + + case AF_INET6: + r = sd_rtnl_message_read_in6_addr(mm, IFA_LOCAL, &address.in6); + if (r < 0) { + r = sd_rtnl_message_read_in6_addr(mm, IFA_ADDRESS, &address.in6); + if (r < 0) + goto fail; } - LIST_FIND_TAIL(addresses, m->fallback_dns, tail); - LIST_INSERT_AFTER(addresses, m->fallback_dns, tail, address); - address = NULL; + break; + + default: + return 0; } + a = link_find_address(l, family, &address); + + switch (type) { + + case RTM_NEWADDR: + + if (!a) { + r = link_address_new(l, &a, family, &address); + if (r < 0) + return r; + } + + r = link_address_update_rtnl(a, mm); + if (r < 0) + return r; + + break; + + case RTM_DELADDR: + if (a) + link_address_free(a); + break; + } + + return 0; + +fail: + log_warning("Failed to process RTNL address message: %s", strerror(-r)); return 0; } -int config_parse_dnsv( - const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { +static int manager_rtnl_listen(Manager *m) { + _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL, *reply = NULL; + sd_rtnl_message *i; + int r; + + assert(m); + + /* First, subscibe to interfaces coming and going */ + r = sd_rtnl_open(&m->rtnl, 3, RTNLGRP_LINK, RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV6_IFADDR); + if (r < 0) + return r; + + r = sd_rtnl_attach_event(m->rtnl, m->event, 0); + if (r < 0) + return r; + + r = sd_rtnl_add_match(m->rtnl, RTM_NEWLINK, manager_process_link, m); + if (r < 0) + return r; + + r = sd_rtnl_add_match(m->rtnl, RTM_DELLINK, manager_process_link, m); + if (r < 0) + return r; + + r = sd_rtnl_add_match(m->rtnl, RTM_NEWADDR, manager_process_address, m); + if (r < 0) + return r; + + r = sd_rtnl_add_match(m->rtnl, RTM_DELADDR, manager_process_address, m); + if (r < 0) + return r; + + /* Then, enumerate all links */ + r = sd_rtnl_message_new_link(m->rtnl, &req, RTM_GETLINK, 0); + if (r < 0) + return r; + + r = sd_rtnl_message_request_dump(req, true); + if (r < 0) + return r; + + r = sd_rtnl_call(m->rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (i = reply; i; i = sd_rtnl_message_next(i)) { + r = manager_process_link(m->rtnl, i, m); + if (r < 0) + return r; + } + + req = sd_rtnl_message_unref(req); + reply = sd_rtnl_message_unref(reply); + + /* Finally, enumerate all addresses, too */ + r = sd_rtnl_message_new_addr(m->rtnl, &req, RTM_GETADDR, 0, AF_UNSPEC); + if (r < 0) + return r; + + r = sd_rtnl_message_request_dump(req, true); + if (r < 0) + return r; + r = sd_rtnl_call(m->rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (i = reply; i; i = sd_rtnl_message_next(i)) { + r = manager_process_address(m->rtnl, i, m); + if (r < 0) + return r; + } + + return r; +} + +static int on_network_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) { Manager *m = userdata; - Address *address; + Iterator i; + Link *l; + int r; - assert(filename); - assert(lvalue); - assert(rvalue); assert(m); - while ((address = m->fallback_dns)) { - LIST_REMOVE(addresses, m->fallback_dns, address); - free(address); + sd_network_monitor_flush(m->network_monitor); + + HASHMAP_FOREACH(l, m->links, i) { + r = link_update_monitor(l); + if (r < 0) + log_warning("Failed to update monitor information for %i: %s", l->ifindex, strerror(-r)); } - set_fallback_dns(m, rvalue); + r = manager_write_resolv_conf(m); + if (r < 0) + log_warning("Could not update resolv.conf: %s", strerror(-r)); return 0; } -static int manager_parse_config_file(Manager *m) { - static const char fn[] = "/etc/systemd/resolved.conf"; - _cleanup_fclose_ FILE *f = NULL; +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; + + return 0; +} + +static int determine_hostname(char **ret) { + _cleanup_free_ char *h = NULL, *n = NULL; + int r; + + assert(ret); + + h = gethostname_malloc(); + if (!h) + return log_oom(); + + if (!utf8_is_valid(h)) { + log_error("System hostname is not UTF-8 clean."); + return -EINVAL; + } + + r = dns_name_normalize(h, &n); + if (r < 0) { + log_error("System hostname '%s' cannot be normalized.", h); + return r; + } + + *ret = n; + n = NULL; + + return 0; +} + +static int on_hostname_change(sd_event_source *es, int fd, uint32_t revents, void *userdata) { + _cleanup_free_ char *h = NULL; + Manager *m = userdata; int r; assert(m); - f = fopen(fn, "re"); - if (!f) { - if (errno == ENOENT) - return 0; + r = determine_hostname(&h); + if (r < 0) + return 0; /* ignore invalid hostnames */ - log_warning("Failed to open configuration file %s: %m", fn); - return -errno; + if (streq(h, m->hostname)) + return 0; + + log_info("System hostname changed to '%s'.", h); + free(m->hostname); + m->hostname = h; + h = 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("Failed to watch hostname: %m"); + return 0; } - r = config_parse(NULL, fn, f, "Resolve\0", config_item_perf_lookup, - (void*) resolved_gperf_lookup, false, false, m); + 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 { + log_error("Failed to add hostname event source: %s", strerror(-r)); + return r; + } + } + + r = determine_hostname(&m->hostname); + if (r < 0) { + log_info("Defaulting to hostname 'linux'."); + m->hostname = strdup("linux"); + if (!m->hostname) + return log_oom(); + } else + log_info("Using system hostname '%s'.", m->hostname); + + return 0; +} + +static 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); +} + +static int manager_llmnr_start(Manager *m) { + int r; + + assert(m); + + if (m->llmnr_support == SUPPORT_NO) + return 0; + + r = manager_llmnr_ipv4_udp_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; if (r < 0) - log_warning("Failed to parse configuration file: %s", strerror(-r)); + return r; - 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 = SUPPORT_NO; + manager_llmnr_stop(m); + + return 0; } int manager_new(Manager **ret) { - _cleanup_manager_free_ Manager *m = NULL; + _cleanup_(manager_freep) Manager *m = NULL; int r; + assert(ret); + m = new0(Manager, 1); if (!m) return -ENOMEM; - r = set_fallback_dns(m, DNS_SERVERS); - if (r < 0) - return r; + m->dns_ipv4_fd = m->dns_ipv6_fd = -1; + m->llmnr_ipv4_udp_fd = m->llmnr_ipv6_udp_fd = -1; + m->llmnr_ipv4_tcp_fd = m->llmnr_ipv6_tcp_fd = -1; + m->hostname_fd = -1; + + m->llmnr_support = SUPPORT_YES; + m->read_resolv_conf = true; - r = manager_parse_config_file(m); + r = manager_parse_dns_server(m, DNS_SERVER_FALLBACK, DNS_SERVERS); if (r < 0) return r; @@ -145,176 +488,1351 @@ int manager_new(Manager **ret) { 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; + *ret = m; m = NULL; return 0; } -void manager_free(Manager *m) { - Address *address; +int manager_start(Manager *m) { + int r; + + assert(m); + + r = manager_llmnr_start(m); + if (r < 0) + return r; + + return 0; +} + +Manager *manager_free(Manager *m) { + Link *l; if (!m) - return; + return NULL; + + 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); + + manager_flush_dns_servers(m, DNS_SERVER_SYSTEM); + manager_flush_dns_servers(m, DNS_SERVER_FALLBACK); + + 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_event_source_unref(m->dns_ipv4_event_source); + sd_event_source_unref(m->dns_ipv6_event_source); + safe_close(m->dns_ipv4_fd); + safe_close(m->dns_ipv6_fd); + + manager_llmnr_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_unref(m->event); - while ((address = m->fallback_dns)) { - LIST_REMOVE(addresses, m->fallback_dns, address); - free(address); - } + dns_resource_key_unref(m->host_ipv4_key); + dns_resource_key_unref(m->host_ipv6_key); + + safe_close(m->hostname_fd); + sd_event_source_unref(m->hostname_event_source); + free(m->hostname); free(m); + + return NULL; } -static void append_dns(FILE *f, void *dns, unsigned char family, unsigned *count) { - char buf[INET6_ADDRSTRLEN]; - const char *address; +int manager_read_resolv_conf(Manager *m) { + _cleanup_fclose_ FILE *f = NULL; + struct stat st, own; + char line[LINE_MAX]; + DnsServer *s, *nx; + 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) + log_warning("Failed to open /etc/resolv.conf: %m"); + r = -errno; + goto clear; + } + + /* Have we already seen the file? */ + t = timespec_load(&st.st_mtim); + if (t == m->resolv_conf_mtime) + return 0; + + m->resolv_conf_mtime = t; + + /* 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) { + r = 0; + goto clear; + } + + f = fopen("/etc/resolv.conf", "re"); + if (!f) { + if (errno != ENOENT) + log_warning("Failed to open /etc/resolv.conf: %m"); + r = -errno; + goto clear; + } + + if (fstat(fileno(f), &st) < 0) { + log_error("Failed to stat open file: %m"); + r = -errno; + goto clear; + } + + LIST_FOREACH(servers, s, m->dns_servers) + s->marked = true; + + FOREACH_LINE(line, f, r = -errno; goto clear) { + union in_addr_union address; + int family; + char *l; + const char *a; + + truncate_nl(line); + + l = strstrip(line); + if (*l == '#' || *l == ';') + continue; + + a = first_word(l, "nameserver"); + if (!a) + continue; + + r = in_addr_from_string_auto(a, &family, &address); + if (r < 0) { + log_warning("Failed to parse name server %s.", a); + continue; + } + + LIST_FOREACH(servers, s, m->dns_servers) + if (s->family == family && in_addr_equal(family, &s->address, &address) > 0) + break; + + if (s) + s->marked = false; + else { + r = dns_server_new(m, NULL, DNS_SERVER_SYSTEM, NULL, family, &address); + if (r < 0) + goto clear; + } + } + + LIST_FOREACH_SAFE(servers, s, nx, m->dns_servers) + if (s->marked) + dns_server_free(s); + + return 0; + +clear: + while (m->dns_servers) + dns_server_free(m->dns_servers); + + return r; +} + +static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) { + _cleanup_free_ char *t = NULL; + int r; + + assert(s); assert(f); - assert(dns); assert(count); - address = inet_ntop(family, dns, buf, INET6_ADDRSTRLEN); - if (!address) { - log_warning("Invalid DNS address. Ignoring."); + r = in_addr_to_string(s->family, &s->address, &t); + if (r < 0) { + log_warning("Invalid DNS address. Ignoring: %s", strerror(-r)); return; } if (*count == MAXNS) - fputs("# Too many DNS servers configured, the following entries " - "may be ignored\n", f); + fputs("# Too many DNS servers configured, the following entries may be ignored.\n", f); - fprintf(f, "nameserver %s\n", address); + fprintf(f, "nameserver %s\n", t); + (*count) ++; +} + +static void write_resolv_conf_search(const char *domain, FILE *f, + unsigned *count, unsigned *length) { + assert(domain); + assert(f); + assert(length); + if (*count >= MAXDNSRCH || + *length + strlen(domain) > 256) { + if (*count == MAXDNSRCH) + fputs(" # Too many search domains configured, remaining ones ignored.", f); + if (*length <= 256) + fputs(" # Total length of all search domains is too long, remaining ones ignored.", f); + + return; + } + + fprintf(f, " %s", domain); + + (*length) += strlen(domain); (*count) ++; } -int manager_update_resolv_conf(Manager *m) { - const char *path = "/run/systemd/resolve/resolv.conf"; +static int write_resolv_conf_contents(FILE *f, Set *dns, Set *domains) { + Iterator i; + + fputs("# This file is managed by systemd-resolved(8). Do not edit.\n#\n" + "# Third party programs must not access this file directly, but\n" + "# only through the symlink at /etc/resolv.conf. To manage\n" + "# resolv.conf(5) in a different way, replace the symlink by a\n" + "# static file or a different symlink.\n\n", f); + + if (set_isempty(dns)) + fputs("# No DNS servers known.\n", f); + else { + DnsServer *s; + unsigned count = 0; + + SET_FOREACH(s, dns, i) + write_resolv_conf_server(s, f, &count); + } + + if (!set_isempty(domains)) { + unsigned length = 0, count = 0; + char *domain; + + fputs("search", f); + SET_FOREACH(domain, domains, i) + write_resolv_conf_search(domain, f, &count, &length); + fputs("\n", f); + } + + return fflush_and_check(f); +} + + +int manager_write_resolv_conf(Manager *m) { + static const char path[] = "/run/systemd/resolve/resolv.conf"; _cleanup_free_ char *temp_path = NULL; _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ unsigned *indices = NULL; - Address *address; - unsigned count = 0; - int n, r, i; + _cleanup_set_free_ Set *dns = NULL, *domains = NULL; + DnsServer *s; + Iterator i; + Link *l; + int r; assert(m); - r = fopen_temporary(path, &f, &temp_path); + /* Read the system /etc/resolv.conf first */ + manager_read_resolv_conf(m); + + /* Add the full list to a set, to filter out duplicates */ + dns = set_new(&dns_server_hash_ops); + if (!dns) + return -ENOMEM; + + domains = set_new(&dns_name_hash_ops); + if (!domains) + return -ENOMEM; + + /* First add the system-wide servers */ + LIST_FOREACH(servers, s, m->dns_servers) { + r = set_put(dns, s); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + + /* Then, add the per-link servers and domains */ + HASHMAP_FOREACH(l, m->links, i) { + char **domain; + + LIST_FOREACH(servers, s, l->dns_servers) { + r = set_put(dns, s); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + + if (!l->unicast_scope) + continue; + + STRV_FOREACH(domain, l->unicast_scope->domains) { + r = set_put(domains, *domain); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + } + + /* If we found nothing, add the fallback servers */ + if (set_isempty(dns)) { + LIST_FOREACH(servers, s, m->fallback_dns_servers) { + r = set_put(dns, s); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + } + + r = fopen_temporary_label(path, path, &f, &temp_path); if (r < 0) return r; fchmod(fileno(f), 0644); - fputs("# This file is managed by systemd-resolved(8). Do not edit.\n#\n" - "# Third party programs must not access this file directly, but\n" - "# only through the symlink at /etc/resolv.conf. To manage\n" - "# resolv.conf(5) in a different way, replace the symlink by a\n" - "# static file or a different symlink.\n\n", f); + r = write_resolv_conf_contents(f, dns, domains); + if (r < 0) + goto fail; + + if (rename(temp_path, path) < 0) { + r = -errno; + goto fail; + } + + return 0; + +fail: + unlink(path); + unlink(temp_path); + return r; +} + +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; + int ms = 0, r; + ssize_t l; + + assert(m); + assert(fd >= 0); + assert(ret); + + r = ioctl(fd, FIONREAD, &ms); + if (r < 0) + return -errno; + if (ms < 0) + return -EIO; + + 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) { + if (errno == EAGAIN || errno == EINTR) + return 0; - n = sd_network_get_ifindices(&indices); - if (n < 0) - n = 0; - - for (i = 0; i < n; i++) { - _cleanup_dhcp_lease_unref_ sd_dhcp_lease *lease = NULL; - struct in_addr *nameservers; - struct in6_addr *nameservers6; - size_t nameservers_size; - - r = sd_network_dhcp_use_dns(indices[i]); - if (r > 0) { - r = sd_network_get_dhcp_lease(indices[i], &lease); - if (r >= 0) { - r = sd_dhcp_lease_get_dns(lease, &nameservers, &nameservers_size); - if (r >= 0) { - unsigned j; - - for (j = 0; j < nameservers_size; j++) - append_dns(f, &nameservers[j], AF_INET, &count); - } + return -errno; + } + + if (l <= 0) + return -EIO; + + 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; + + for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg)) { + + 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; } - } - r = sd_network_get_dns(indices[i], &nameservers, &nameservers_size); - if (r >= 0) { - unsigned j; + case IPV6_HOPLIMIT: + p->ttl = *(int *) CMSG_DATA(cmsg); + break; + + } + } else if (cmsg->cmsg_level == IPPROTO_IP) { + assert(p->family == AF_INET); - for (j = 0; j < nameservers_size; j++) - append_dns(f, &nameservers[j], AF_INET, &count); + switch (cmsg->cmsg_type) { - free(nameservers); - } + case IP_PKTINFO: { + struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg); - r = sd_network_get_dns6(indices[i], &nameservers6, &nameservers_size); - if (r >= 0) { - unsigned j; + if (p->ifindex <= 0) + p->ifindex = i->ipi_ifindex; - for (j = 0; j < nameservers_size; j++) - append_dns(f, &nameservers6[j], AF_INET6, &count); + p->destination.in = i->ipi_addr; + break; + } - free(nameservers6); + case IP_TTL: + p->ttl = *(int *) CMSG_DATA(cmsg); + break; + } } } - LIST_FOREACH(addresses, address, m->fallback_dns) - append_dns(f, &address->in_addr, address->family, &count); + /* 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 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 on_dns_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; + int r; + + r = manager_recv(m, fd, DNS_PROTOCOL_DNS, &p); + if (r <= 0) + return r; + + if (dns_packet_validate_reply(p) > 0) { + t = hashmap_get(m->dns_transactions, UINT_TO_PTR(DNS_PACKET_ID(p))); + if (!t) + return 0; + + dns_transaction_process_reply(t, p); + + } else + log_debug("Invalid DNS packet."); + + return 0; +} + +int manager_dns_ipv4_fd(Manager *m) { + const int one = 1; + int r; + + assert(m); + + if (m->dns_ipv4_fd >= 0) + return m->dns_ipv4_fd; + + m->dns_ipv4_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->dns_ipv4_fd < 0) + return -errno; + + r = setsockopt(m->dns_ipv4_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->dns_ipv4_event_source, m->dns_ipv4_fd, EPOLLIN, on_dns_packet, m); + if (r < 0) + goto fail; + + return m->dns_ipv4_fd; + +fail: + m->dns_ipv4_fd = safe_close(m->dns_ipv4_fd); + return r; +} + +int manager_dns_ipv6_fd(Manager *m) { + const int one = 1; + int r; + + assert(m); - fflush(f); + if (m->dns_ipv6_fd >= 0) + return m->dns_ipv6_fd; - if (ferror(f) || rename(temp_path, path) < 0) { + m->dns_ipv6_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->dns_ipv6_fd < 0) + return -errno; + + r = setsockopt(m->dns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); + if (r < 0) { r = -errno; - unlink(path); - unlink(temp_path); + goto fail; + } + + r = sd_event_add_io(m->event, &m->dns_ipv6_event_source, m->dns_ipv6_fd, EPOLLIN, on_dns_packet, m); + if (r < 0) + goto fail; + + return m->dns_ipv6_fd; + +fail: + m->dns_ipv6_fd = safe_close(m->dns_ipv6_fd); + return r; +} + +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 manager_ipv4_send(Manager *m, int fd, int ifindex, const struct in_addr *addr, uint16_t port, DnsPacket *p) { + union sockaddr_union sa = { + .in.sin_family = AF_INET, + }; + 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(addr); + assert(port > 0); + assert(p); + + iov.iov_base = DNS_PACKET_DATA(p); + iov.iov_len = p->size; + + sa.in.sin_addr = *addr; + 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; + } + + return sendmsg_loop(fd, &mh, 0); +} + +static int manager_ipv6_send(Manager *m, int fd, int ifindex, const struct in6_addr *addr, uint16_t port, DnsPacket *p) { + union sockaddr_union sa = { + .in6.sin6_family = AF_INET6, + }; + 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(addr); + assert(port > 0); + assert(p); + + iov.iov_base = DNS_PACKET_DATA(p); + iov.iov_len = p->size; + + sa.in6.sin6_addr = *addr; + 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; + } + + return sendmsg_loop(fd, &mh, 0); +} + +int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *addr, uint16_t port, DnsPacket *p) { + assert(m); + assert(fd >= 0); + assert(addr); + assert(port > 0); + assert(p); + + log_debug("Sending %s packet with id %u on interface %i/%s", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p), ifindex, af_to_name(family)); + + if (family == AF_INET) + return manager_ipv4_send(m, fd, ifindex, &addr->in, port, p); + else if (family == AF_INET6) + return manager_ipv6_send(m, fd, ifindex, &addr->in6, port, p); + + return -EAFNOSUPPORT; +} + +DnsServer* manager_find_dns_server(Manager *m, int family, const union in_addr_union *in_addr) { + DnsServer *s; + + assert(m); + assert(in_addr); + + LIST_FOREACH(servers, s, m->dns_servers) + if (s->family == family && in_addr_equal(family, &s->address, in_addr) > 0) + return s; + + LIST_FOREACH(servers, s, m->fallback_dns_servers) + if (s->family == family && in_addr_equal(family, &s->address, in_addr) > 0) + return s; + + return NULL; +} + +DnsServer *manager_set_dns_server(Manager *m, DnsServer *s) { + assert(m); + + if (m->current_dns_server == s) + return s; + + if (s) { + _cleanup_free_ char *ip = NULL; + + in_addr_to_string(s->family, &s->address, &ip); + log_info("Switching to system DNS server %s.", strna(ip)); + } + + m->current_dns_server = 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 (!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 */ + if (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); +} + +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; +} + +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; + + 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."); + return 0; } + if (dns_packet_validate_reply(p) > 0) { + log_debug("Got 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 query packet for id %u", DNS_PACKET_ID(p)); + + dns_scope_process_query(scope, NULL, p); + } else + log_debug("Invalid LLMNR UDP packet."); + return 0; } -static int manager_network_event_handler(sd_event_source *s, int fd, uint32_t revents, - void *userdata) { - Manager *m = userdata; +int manager_llmnr_ipv4_udp_fd(Manager *m) { + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(5355), + }; + static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255; int r; assert(m); - r = manager_update_resolv_conf(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) - log_warning("Could not update resolv.conf: %s", strerror(-r)); + goto fail; - sd_network_monitor_flush(m->network_monitor); + 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(5355), + }; + 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) { + r = -errno; + goto fail; + } + + 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); + + scope = manager_find_scope(s->manager, s->read_packet); + if (!scope) { + log_warning("Got LLMNR TCP packet on unknown scope. Ignroing."); + return 0; + } + + if (dns_packet_validate_query(s->read_packet) > 0) { + log_debug("Got query packet for id %u", DNS_PACKET_ID(s->read_packet)); + + dns_scope_process_query(scope, s, s->read_packet); + + /* If no reply packet was set, we free the stream */ + if (s->write_packet) + return 0; + } else + log_debug("Invalid LLMNR TCP packet."); + + dns_stream_free(s); return 0; } -int manager_network_monitor_listen(Manager *m) { - _cleanup_event_source_unref_ sd_event_source *event_source = NULL; - _cleanup_network_monitor_unref_ sd_network_monitor *monitor = NULL; - int r, fd, events; +static int on_llmnr_stream(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + DnsStream *stream; + Manager *m = userdata; + int cfd, r; - r = sd_network_monitor_new(NULL, &monitor); - if (r < 0) + 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; + } - fd = sd_network_monitor_get_fd(monitor); - if (fd < 0) - return fd; + stream->on_packet = on_llmnr_stream_packet; + return 0; +} - events = sd_network_monitor_get_events(monitor); - if (events < 0) - return events; +int manager_llmnr_ipv4_tcp_fd(Manager *m) { + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(5355), + }; + static const int one = 1, pmtu = IP_PMTUDISC_DONT; + int r; + + assert(m); - r = sd_event_add_io(m->event, &event_source, fd, events, - &manager_network_event_handler, 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) - return r; + goto fail; + + return m->llmnr_ipv4_tcp_fd; + +fail: + m->llmnr_ipv4_tcp_fd = safe_close(m->llmnr_ipv4_tcp_fd); + return r; +} + +int manager_llmnr_ipv6_tcp_fd(Manager *m) { + union sockaddr_union sa = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = htobe16(5355), + }; + static const int one = 1; + int r; + + assert(m); - m->network_monitor = monitor; - m->network_event_source = event_source; - monitor = NULL; - event_source = NULL; + 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) { + r = -errno; + goto fail; + } + + return m->llmnr_ipv6_tcp_fd; + +fail: + m->llmnr_ipv6_tcp_fd = safe_close(m->llmnr_ipv6_tcp_fd); + return r; +} + +int manager_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->host_ipv4_key = dns_resource_key_unref(m->host_ipv4_key); + m->host_ipv6_key = dns_resource_key_unref(m->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; + + assert(m); + + p = strchr(m->hostname, 0); + assert(p); + + while (p > m->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->hostname), m->hostname, u) < 0) + return -ENOMEM; + + log_info("Hostname conflict, changing published hostname from '%s' to '%s'.", m->hostname, h); + + free(m->hostname); + m->hostname = h; + + 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; + + if (p->protocol == DNS_PROTOCOL_LLMNR) { + if (p->family == AF_INET) + return l->llmnr_ipv4_scope; + else if (p->family == AF_INET6) + return l->llmnr_ipv6_scope; + } + + 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); +} + +void manager_flush_dns_servers(Manager *m, DnsServerType t) { + assert(m); + + if (t == DNS_SERVER_SYSTEM) + while (m->dns_servers) + dns_server_free(m->dns_servers); + + if (t == DNS_SERVER_FALLBACK) + while (m->fallback_dns_servers) + dns_server_free(m->fallback_dns_servers); +} + +static const char* const support_table[_SUPPORT_MAX] = { + [SUPPORT_NO] = "no", + [SUPPORT_YES] = "yes", + [SUPPORT_RESOLVE] = "resolve", +}; +DEFINE_STRING_TABLE_LOOKUP(support, Support); diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h new file mode 100644 index 0000000000..1151029d29 --- /dev/null +++ b/src/resolve/resolved-manager.h @@ -0,0 +1,160 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#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-network.h" +#include "sd-rtnl.h" +#include "util.h" +#include "list.h" +#include "in-addr-util.h" +#include "hashmap.h" + +typedef struct Manager Manager; +typedef enum Support Support; + +enum Support { + SUPPORT_NO, + SUPPORT_YES, + SUPPORT_RESOLVE, + _SUPPORT_MAX, + _SUPPORT_INVALID = -1 +}; + +#include "resolved-dns-query.h" +#include "resolved-dns-server.h" +#include "resolved-dns-scope.h" +#include "resolved-dns-stream.h" +#include "resolved-link.h" + +struct Manager { + sd_event *event; + + Support llmnr_support; + + /* Network */ + Hashmap *links; + + sd_rtnl *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 */ + int dns_ipv4_fd; + int dns_ipv6_fd; + + sd_event_source *dns_ipv4_event_source; + sd_event_source *dns_ipv6_event_source; + + LIST_HEAD(DnsServer, dns_servers); + LIST_HEAD(DnsServer, fallback_dns_servers); + DnsServer *current_dns_server; + + bool read_resolv_conf; + usec_t resolv_conf_mtime; + + 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; + + /* dbus */ + sd_bus *bus; + sd_event_source *bus_retry_event_source; + + /* The hostname we publish on LLMNR and mDNS */ + char *hostname; + DnsResourceKey *host_ipv4_key; + DnsResourceKey *host_ipv6_key; + + /* Watch the system hostname */ + int hostname_fd; + sd_event_source *hostname_event_source; + + /* Watch for system suspends */ + sd_bus_slot *prepare_for_sleep_slot; +}; + +/* Manager */ + +int manager_new(Manager **ret); +Manager* manager_free(Manager *m); + +int manager_start(Manager *m); +int manager_read_resolv_conf(Manager *m); +int manager_write_resolv_conf(Manager *m); + +DnsServer *manager_set_dns_server(Manager *m, DnsServer *s); +DnsServer *manager_find_dns_server(Manager *m, int family, const union in_addr_union *in_addr); +DnsServer *manager_get_dns_server(Manager *m); +void manager_next_dns_server(Manager *m); + +uint32_t manager_find_mtu(Manager *m); + +int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *addr, uint16_t port, DnsPacket *p); +int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret); + +int manager_dns_ipv4_fd(Manager *m); +int manager_dns_ipv6_fd(Manager *m); +int manager_llmnr_ipv4_udp_fd(Manager *m); +int manager_llmnr_ipv6_udp_fd(Manager *m); +int manager_llmnr_ipv4_tcp_fd(Manager *m); +int manager_llmnr_ipv6_tcp_fd(Manager *m); + +int manager_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); + +void manager_flush_dns_servers(Manager *m, DnsServerType t); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); + +#define EXTRA_CMSG_SPACE 1024 + +const char* support_to_string(Support p) _const_; +int support_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c index f61b70f46b..7d258c9470 100644 --- a/src/resolve/resolved.c +++ b/src/resolve/resolved.c @@ -21,14 +21,15 @@ #include "sd-event.h" #include "sd-daemon.h" - -#include "resolved.h" - #include "mkdir.h" +#include "label.h" #include "capability.h" +#include "resolved-manager.h" +#include "resolved-conf.h" + int main(int argc, char *argv[]) { - _cleanup_manager_free_ Manager *m = NULL; + _cleanup_(manager_freep) Manager *m = NULL; const char *user = "systemd-resolve"; uid_t uid; gid_t gid; @@ -38,52 +39,61 @@ int main(int argc, char *argv[]) { log_parse_environment(); log_open(); - umask(0022); - if (argc != 1) { log_error("This program takes no arguments."); r = -EINVAL; - goto out; + goto finish; + } + + umask(0022); + + r = mac_selinux_init(NULL); + if (r < 0) { + log_error("SELinux setup failed: %s", strerror(-r)); + goto finish; } r = get_user_creds(&user, &uid, &gid, NULL, NULL); if (r < 0) { log_error("Cannot resolve user name %s: %s", user, strerror(-r)); - goto out; + goto finish; } /* Always create the directory where resolv.conf will live */ r = mkdir_safe_label("/run/systemd/resolve", 0755, uid, gid); if (r < 0) { - log_error("Could not create runtime directory: %s", - strerror(-r)); - goto out; + log_error("Could not create runtime directory: %s", strerror(-r)); + goto finish; } r = drop_privileges(uid, gid, 0); if (r < 0) - goto out; + goto finish; + + assert_se(sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1) == 0); r = manager_new(&m); if (r < 0) { log_error("Could not create manager: %s", strerror(-r)); - goto out; + goto finish; } - r = manager_network_monitor_listen(m); - if (r < 0) { - log_error("Could not listen for network events: %s", strerror(-r)); - goto out; - } + r = manager_parse_config_file(m); + if (r < 0) + log_warning("Failed to parse configuration file: %s", strerror(-r)); - /* write out default resolv.conf to avoid a - * dangling symlink */ - r = manager_update_resolv_conf(m); + r = manager_start(m); if (r < 0) { - log_error("Could not create resolv.conf: %s", strerror(-r)); - goto out; + log_error("Failed to start manager: %s", strerror(-r)); + goto finish; } + /* Write finish default resolv.conf to avoid a dangling + * symlink */ + r = manager_write_resolv_conf(m); + if (r < 0) + log_warning("Could not create resolv.conf: %s", strerror(-r)); + sd_notify(false, "READY=1\n" "STATUS=Processing requests..."); @@ -91,11 +101,14 @@ int main(int argc, char *argv[]) { r = sd_event_loop(m->event); if (r < 0) { log_error("Event loop failed: %s", strerror(-r)); - goto out; + goto finish; } -out: + sd_event_get_exit_code(m->event, &r); + +finish: sd_notify(false, + "STOPPIN=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 index a2391954aa..c8263d67f4 100644 --- a/src/resolve/resolved.conf.in +++ b/src/resolve/resolved.conf.in @@ -8,4 +8,6 @@ # See resolved.conf(5) for details [Resolve] -#DNS=@DNS_SERVERS@ +#DNS= +#FallbackDNS=@DNS_SERVERS@ +#LLMNR=yes diff --git a/src/resolve/resolved.h b/src/resolve/resolved.h deleted file mode 100644 index 984edc76c1..0000000000 --- a/src/resolve/resolved.h +++ /dev/null @@ -1,69 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2013 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/>. -***/ - -#pragma once - -#include "sd-event.h" -#include "sd-network.h" - -#include "util.h" -#include "list.h" - -typedef struct Address Address; -typedef struct Manager Manager; - -struct Address { - unsigned char family; - - union { - struct in_addr in; - struct in6_addr in6; - } in_addr; - - LIST_FIELDS(Address, addresses); -}; - -struct Manager { - sd_event *event; - - LIST_HEAD(Address, fallback_dns); - - /* network */ - sd_event_source *network_event_source; - sd_network_monitor *network_monitor; -}; - -/* Manager */ - -int manager_new(Manager **ret); -void manager_free(Manager *m); - -int manager_update_resolv_conf(Manager *m); -int manager_network_monitor_listen(Manager *m); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); -#define _cleanup_manager_free_ _cleanup_(manager_freep) - -const struct ConfigPerfItem* resolved_gperf_lookup(const char *key, unsigned length); - -int config_parse_dnsv(const char *unit, const char *filename, unsigned line, - const char *section, unsigned section_line, const char *lvalue, - int ltype, const char *rvalue, void *data, void *userdata); diff --git a/src/resolve/test-dns-domain.c b/src/resolve/test-dns-domain.c new file mode 100644 index 0000000000..dfe2a44eae --- /dev/null +++ b/src/resolve/test-dns-domain.c @@ -0,0 +1,192 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. + ***/ + +#include "log.h" +#include "resolved-dns-domain.h" + +static void test_dns_label_unescape_one(const char *what, const char *expect, size_t buffer_sz, int ret) { + char buffer[buffer_sz]; + int r; + + r = dns_label_unescape(&what, buffer, buffer_sz); + assert_se(r == ret); + + if (r < 0) + return; + + assert_se(streq(buffer, expect)); +} + +static void test_dns_label_unescape(void) { + test_dns_label_unescape_one("hallo", "hallo", 6, 5); + test_dns_label_unescape_one("hallo", "hallo", 4, -ENOSPC); + test_dns_label_unescape_one("", "", 10, 0); + test_dns_label_unescape_one("hallo\\.foobar", "hallo.foobar", 20, 12); + test_dns_label_unescape_one("hallo.foobar", "hallo", 10, 5); + test_dns_label_unescape_one("hallo\n.foobar", "hallo", 20, -EINVAL); + test_dns_label_unescape_one("hallo\\", "hallo", 20, -EINVAL); + test_dns_label_unescape_one("hallo\\032 ", "hallo ", 20, 7); + test_dns_label_unescape_one(".", "", 20, 0); + test_dns_label_unescape_one("..", "", 20, -EINVAL); + test_dns_label_unescape_one(".foobar", "", 20, -EINVAL); + test_dns_label_unescape_one("foobar.", "foobar", 20, 6); +} + +static void test_dns_label_escape_one(const char *what, size_t l, const char *expect, int ret) { + _cleanup_free_ char *t = NULL; + int r; + + r = dns_label_escape(what, l, &t); + assert(r == ret); + + if (r < 0) + return; + + assert_se(streq_ptr(expect, t)); +} + +static void test_dns_label_escape(void) { + test_dns_label_escape_one("", 0, "", 0); + test_dns_label_escape_one("hallo", 5, "hallo", 5); + test_dns_label_escape_one("hallo", 6, NULL, -EINVAL); + test_dns_label_escape_one("hallo hallo.foobar,waldi", 24, "hallo\\032hallo\\.foobar\\044waldi", 31); +} + +static void test_dns_name_normalize_one(const char *what, const char *expect, int ret) { + _cleanup_free_ char *t = NULL; + int r; + + r = dns_name_normalize(what, &t); + assert_se(r == ret); + + if (r < 0) + return; + + assert_se(streq_ptr(expect, t)); +} + +static void test_dns_name_normalize(void) { + test_dns_name_normalize_one("", "", 0); + test_dns_name_normalize_one("f", "f", 0); + test_dns_name_normalize_one("f.waldi", "f.waldi", 0); + test_dns_name_normalize_one("f \\032.waldi", "f\\032\\032.waldi", 0); + test_dns_name_normalize_one("\\000", NULL, -EINVAL); + test_dns_name_normalize_one("..", NULL, -EINVAL); + test_dns_name_normalize_one(".foobar", NULL, -EINVAL); + test_dns_name_normalize_one("foobar.", "foobar", 0); + test_dns_name_normalize_one(".", "", 0); +} + +static void test_dns_name_equal_one(const char *a, const char *b, int ret) { + int r; + + r = dns_name_equal(a, b); + assert_se(r == ret); + + r = dns_name_equal(b, a); + assert_se(r == ret); +} + +static void test_dns_name_equal(void) { + test_dns_name_equal_one("", "", true); + test_dns_name_equal_one("x", "x", true); + test_dns_name_equal_one("x", "x.", true); + test_dns_name_equal_one("abc.def", "abc.def", true); + test_dns_name_equal_one("abc.def", "ABC.def", true); + test_dns_name_equal_one("abc.def", "CBA.def", false); + test_dns_name_equal_one("", "xxx", false); + test_dns_name_equal_one("ab", "a", false); + test_dns_name_equal_one("\\000", "xxxx", -EINVAL); + test_dns_name_equal_one(".", "", true); + test_dns_name_equal_one(".", ".", true); + test_dns_name_equal_one("..", "..", -EINVAL); +} + +static void test_dns_name_endswith_one(const char *a, const char *b, int ret) { + assert_se(dns_name_endswith(a, b) == ret); +} + +static void test_dns_name_endswith(void) { + test_dns_name_endswith_one("", "", true); + test_dns_name_endswith_one("", "xxx", false); + test_dns_name_endswith_one("xxx", "", true); + test_dns_name_endswith_one("x", "x", true); + test_dns_name_endswith_one("x", "y", false); + test_dns_name_endswith_one("x.y", "y", true); + test_dns_name_endswith_one("x.y", "Y", true); + test_dns_name_endswith_one("x.y", "x", false); + test_dns_name_endswith_one("x.y.z", "Z", true); + test_dns_name_endswith_one("x.y.z", "y.Z", true); + test_dns_name_endswith_one("x.y.z", "x.y.Z", true); + test_dns_name_endswith_one("x.y.z", "waldo", false); + test_dns_name_endswith_one("x.y.z.u.v.w", "y.z", false); + test_dns_name_endswith_one("x.y.z.u.v.w", "u.v.w", true); + test_dns_name_endswith_one("x.y\001.z", "waldo", -EINVAL); +} + +static void test_dns_name_root(void) { + assert_se(dns_name_root("") == true); + assert_se(dns_name_root(".") == true); + assert_se(dns_name_root("xxx") == false); + assert_se(dns_name_root("xxx.") == false); + assert_se(dns_name_root("..") == -EINVAL); +} + +static void test_dns_name_single_label(void) { + assert_se(dns_name_single_label("") == false); + assert_se(dns_name_single_label(".") == false); + assert_se(dns_name_single_label("..") == -EINVAL); + assert_se(dns_name_single_label("x") == true); + assert_se(dns_name_single_label("x.") == true); + assert_se(dns_name_single_label("xx.yy") == false); +} + +static void test_dns_name_reverse_one(const char *address, const char *name) { + _cleanup_free_ char *p = NULL; + union in_addr_union a, b; + int familya, familyb; + + assert_se(in_addr_from_string_auto(address, &familya, &a) >= 0); + assert_se(dns_name_reverse(familya, &a, &p) >= 0); + assert_se(streq(p, name)); + assert_se(dns_name_address(p, &familyb, &b) > 0); + assert_se(familya == familyb); + assert_se(in_addr_equal(familya, &a, &b)); +} + +static void test_dns_name_reverse(void) { + test_dns_name_reverse_one("47.11.8.15", "15.8.11.47.in-addr.arpa"); + test_dns_name_reverse_one("fe80::47", "7.4.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.8.e.f.ip6.arpa"); +} + +int main(int argc, char *argv[]) { + + test_dns_label_unescape(); + test_dns_label_escape(); + test_dns_name_normalize(); + test_dns_name_equal(); + test_dns_name_endswith(); + test_dns_name_root(); + test_dns_name_single_label(); + test_dns_name_reverse(); + + return 0; +} |