summaryrefslogtreecommitdiff
path: root/src/resolve
diff options
context:
space:
mode:
authorMartin Pitt <martin.pitt@ubuntu.com>2014-11-20 15:28:12 +0100
committerMartin Pitt <martin.pitt@ubuntu.com>2014-11-20 15:28:12 +0100
commit5eef597e931b0428bb984dc2bf0736d032a9198c (patch)
treec24d24d36d083e227a6ba3c1b5ae5c40ae822cb2 /src/resolve
parente842803ae5f3f162fa51f30787c3b2423e3826cf (diff)
downloadsystemd-5eef597e931b0428bb984dc2bf0736d032a9198c.tar.gz
Imported Upstream version 217
Diffstat (limited to 'src/resolve')
-rw-r--r--src/resolve/dns-type.c45
-rw-r--r--src/resolve/dns-type.h120
-rw-r--r--src/resolve/dns_type-from-name.gperf79
-rw-r--r--src/resolve/dns_type-from-name.h283
-rw-r--r--src/resolve/dns_type-to-name.h83
-rw-r--r--src/resolve/org.freedesktop.resolve1.conf27
-rw-r--r--src/resolve/org.freedesktop.resolve1.service12
-rw-r--r--src/resolve/resolved-bus.c761
-rw-r--r--src/resolve/resolved-bus.h26
-rw-r--r--src/resolve/resolved-conf.c154
-rw-r--r--src/resolve/resolved-conf.h32
-rw-r--r--src/resolve/resolved-def.h30
-rw-r--r--src/resolve/resolved-dns-answer.c238
-rw-r--r--src/resolve/resolved-dns-answer.h48
-rw-r--r--src/resolve/resolved-dns-cache.c564
-rw-r--r--src/resolve/resolved-dns-cache.h46
-rw-r--r--src/resolve/resolved-dns-domain.c613
-rw-r--r--src/resolve/resolved-dns-domain.h49
-rw-r--r--src/resolve/resolved-dns-packet.c1461
-rw-r--r--src/resolve/resolved-dns-packet.h236
-rw-r--r--src/resolve/resolved-dns-query.c489
-rw-r--r--src/resolve/resolved-dns-query.h86
-rw-r--r--src/resolve/resolved-dns-question.c274
-rw-r--r--src/resolve/resolved-dns-question.h52
-rw-r--r--src/resolve/resolved-dns-rr.c704
-rw-r--r--src/resolve/resolved-dns-rr.h177
-rw-r--r--src/resolve/resolved-dns-scope.c797
-rw-r--r--src/resolve/resolved-dns-scope.h88
-rw-r--r--src/resolve/resolved-dns-server.c127
-rw-r--r--src/resolve/resolved-dns-server.h63
-rw-r--r--src/resolve/resolved-dns-stream.c402
-rw-r--r--src/resolve/resolved-dns-stream.h64
-rw-r--r--src/resolve/resolved-dns-transaction.c619
-rw-r--r--src/resolve/resolved-dns-transaction.h110
-rw-r--r--src/resolve/resolved-dns-zone.c648
-rw-r--r--src/resolve/resolved-dns-zone.h80
-rw-r--r--src/resolve/resolved-gperf.gperf6
-rw-r--r--src/resolve/resolved-link.c552
-rw-r--r--src/resolve/resolved-link.h91
-rw-r--r--src/resolve/resolved-manager.c1832
-rw-r--r--src/resolve/resolved-manager.h160
-rw-r--r--src/resolve/resolved.c63
-rw-r--r--src/resolve/resolved.conf.in4
-rw-r--r--src/resolve/resolved.h69
-rw-r--r--src/resolve/test-dns-domain.c192
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;
+}