summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/basic/escape.c16
-rw-r--r--src/basic/escape.h1
-rw-r--r--src/resolve-host/resolve-host.c342
-rw-r--r--src/resolve/resolved-bus.c840
-rw-r--r--src/resolve/resolved-def.h8
-rw-r--r--src/resolve/resolved-dns-answer.h17
-rw-r--r--src/resolve/resolved-dns-packet.c96
-rw-r--r--src/resolve/resolved-dns-packet.h8
-rw-r--r--src/resolve/resolved-dns-query.c94
-rw-r--r--src/resolve/resolved-dns-query.h18
-rw-r--r--src/resolve/resolved-dns-question.c126
-rw-r--r--src/resolve/resolved-dns-question.h8
-rw-r--r--src/resolve/resolved-dns-rr.c73
-rw-r--r--src/resolve/resolved-dns-rr.h13
-rw-r--r--src/resolve/resolved-manager.c3
-rw-r--r--src/shared/dns-domain.c226
-rw-r--r--src/shared/dns-domain.h7
-rw-r--r--src/test/test-dns-domain.c96
18 files changed, 1728 insertions, 264 deletions
diff --git a/src/basic/escape.c b/src/basic/escape.c
index 4815161b09..42a84c9317 100644
--- a/src/basic/escape.c
+++ b/src/basic/escape.c
@@ -89,20 +89,20 @@ size_t cescape_char(char c, char *buf) {
return buf - buf_old;
}
-char *cescape(const char *s) {
- char *r, *t;
+char *cescape_length(const char *s, size_t n) {
const char *f;
+ char *r, *t;
- assert(s);
+ assert(s || n == 0);
/* Does C style string escaping. May be reversed with
* cunescape(). */
- r = new(char, strlen(s)*4 + 1);
+ r = new(char, n*4 + 1);
if (!r)
return NULL;
- for (f = s, t = r; *f; f++)
+ for (f = s, t = r; f < s + n; f++)
t += cescape_char(*f, t);
*t = 0;
@@ -110,6 +110,12 @@ char *cescape(const char *s) {
return r;
}
+char *cescape(const char *s) {
+ assert(s);
+
+ return cescape_length(s, strlen(s));
+}
+
int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_unicode) {
int r = 1;
diff --git a/src/basic/escape.h b/src/basic/escape.h
index 30604c58f9..52ebf11c4a 100644
--- a/src/basic/escape.h
+++ b/src/basic/escape.h
@@ -35,6 +35,7 @@ typedef enum UnescapeFlags {
} UnescapeFlags;
char *cescape(const char *s);
+char *cescape_length(const char *s, size_t n);
size_t cescape_char(char c, char *buf);
int cunescape(const char *s, UnescapeFlags flags, char **ret);
diff --git a/src/resolve-host/resolve-host.c b/src/resolve-host/resolve-host.c
index eb4e646846..2c17ad6ede 100644
--- a/src/resolve-host/resolve-host.c
+++ b/src/resolve-host/resolve-host.c
@@ -28,6 +28,7 @@
#include "alloc-util.h"
#include "bus-error.h"
#include "bus-util.h"
+#include "escape.h"
#include "in-addr-util.h"
#include "parse-util.h"
#include "resolved-def.h"
@@ -41,6 +42,7 @@ static int arg_type = 0;
static uint16_t arg_class = 0;
static bool arg_legend = true;
static uint64_t arg_flags = 0;
+static bool arg_resolve_service = false;
static void print_source(uint64_t flags, usec_t rtt) {
char rtt_str[FORMAT_TIMESTAMP_MAX];
@@ -102,10 +104,8 @@ static int resolve_host(sd_bus *bus, const char *name) {
ts = now(CLOCK_MONOTONIC);
r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
- if (r < 0) {
- log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r));
- return r;
- }
+ if (r < 0)
+ return log_error_errno(r, "%s: resolve call failed: %s", name, bus_error_message(&error, r));
ts = now(CLOCK_MONOTONIC) - ts;
@@ -114,10 +114,10 @@ static int resolve_host(sd_bus *bus, const char *name) {
return bus_log_parse_error(r);
while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) {
- const void *a;
- size_t sz;
_cleanup_free_ char *pretty = NULL;
int ifindex, family;
+ const void *a;
+ size_t sz;
assert_cc(sizeof(int) == sizeof(int32_t));
@@ -140,7 +140,7 @@ static int resolve_host(sd_bus *bus, const char *name) {
if (sz != FAMILY_ADDRESS_SIZE(family)) {
log_error("%s: systemd-resolved returned address of invalid size %zu for family %s", name, sz, af_to_name(family) ?: "unknown");
- continue;
+ return -EINVAL;
}
ifname[0] = 0;
@@ -437,6 +437,207 @@ static int resolve_record(sd_bus *bus, const char *name) {
return 0;
}
+static int resolve_service(sd_bus *bus, const char *name, const char *type, const char *domain) {
+ const char *canonical_name, *canonical_type, *canonical_domain;
+ _cleanup_bus_message_unref_ sd_bus_message *req = NULL, *reply = NULL;
+ _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
+ char ifname[IF_NAMESIZE] = "";
+ size_t indent, sz;
+ uint64_t flags;
+ const char *p;
+ unsigned c;
+ usec_t ts;
+ int r;
+
+ assert(bus);
+ assert(domain);
+
+ if (isempty(name))
+ name = NULL;
+ if (isempty(type))
+ type = NULL;
+
+ if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname))
+ return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex);
+
+ if (name)
+ log_debug("Resolving service \"%s\" of type %s in %s (family %s, interface %s).", name, type, domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname);
+ else if (type)
+ log_debug("Resolving service type %s of %s (family %s, interface %s).", type, domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname);
+ else
+ log_debug("Resolving service type %s (family %s, interface %s).", domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname);
+
+ r = sd_bus_message_new_method_call(
+ bus,
+ &req,
+ "org.freedesktop.resolve1",
+ "/org/freedesktop/resolve1",
+ "org.freedesktop.resolve1.Manager",
+ "ResolveService");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append(req, "isssit", arg_ifindex, name, type, domain, arg_family, arg_flags);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ ts = now(CLOCK_MONOTONIC);
+
+ r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
+ if (r < 0)
+ return log_error_errno(r, "Resolve call failed: %s", bus_error_message(&error, r));
+
+ ts = now(CLOCK_MONOTONIC) - ts;
+
+ r = sd_bus_message_enter_container(reply, 'a', "(qqqsa(iiay)s)");
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ indent =
+ (name ? strlen(name) + 1 : 0) +
+ (type ? strlen(type) + 1 : 0) +
+ strlen(domain) + 2;
+
+ c = 0;
+ while ((r = sd_bus_message_enter_container(reply, 'r', "qqqsa(iiay)s")) > 0) {
+ uint16_t priority, weight, port;
+ const char *hostname, *canonical;
+
+ r = sd_bus_message_read(reply, "qqqs", &priority, &weight, &port, &hostname);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ if (name)
+ printf("%*s%s", (int) strlen(name), c == 0 ? name : "", c == 0 ? "/" : " ");
+ if (type)
+ printf("%*s%s", (int) strlen(type), c == 0 ? type : "", c == 0 ? "/" : " ");
+
+ printf("%*s%s %s:%u [priority=%u, weight=%u]\n",
+ (int) strlen(domain), c == 0 ? domain : "",
+ c == 0 ? ":" : " ",
+ hostname, port,
+ priority, weight);
+
+ r = sd_bus_message_enter_container(reply, 'a', "(iiay)");
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) {
+ _cleanup_free_ char *pretty = NULL;
+ int ifindex, family;
+ const void *a;
+
+ assert_cc(sizeof(int) == sizeof(int32_t));
+
+ r = sd_bus_message_read(reply, "ii", &ifindex, &family);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ r = sd_bus_message_read_array(reply, 'y', &a, &sz);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ if (!IN_SET(family, AF_INET, AF_INET6)) {
+ log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown");
+ continue;
+ }
+
+ if (sz != FAMILY_ADDRESS_SIZE(family)) {
+ log_error("%s: systemd-resolved returned address of invalid size %zu for family %s", name, sz, af_to_name(family) ?: "unknown");
+ return -EINVAL;
+ }
+
+ ifname[0] = 0;
+ if (ifindex > 0 && !if_indextoname(ifindex, ifname))
+ log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex);
+
+ r = in_addr_to_string(family, a, &pretty);
+ if (r < 0)
+ return log_error_errno(r, "Failed to print address for %s: %m", name);
+
+ printf("%*s%s%s%s\n", (int) indent, "", pretty, isempty(ifname) ? "" : "%s", ifname);
+ }
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ r = sd_bus_message_read(reply, "s", &canonical);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ if (!streq(hostname, canonical))
+ printf("%*s(%s)\n", (int) indent, "", canonical);
+
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ c++;
+ }
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ r = sd_bus_message_enter_container(reply, 'a', "ay");
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ c = 0;
+ while ((r = sd_bus_message_read_array(reply, 'y', (const void**) &p, &sz)) > 0) {
+ _cleanup_free_ char *escaped = NULL;
+
+ escaped = cescape_length(p, sz);
+ if (!escaped)
+ return log_oom();
+
+ printf("%*s%s\n", (int) indent, "", escaped);
+ c++;
+ }
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ r = sd_bus_message_exit_container(reply);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ r = sd_bus_message_read(reply, "ssst", &canonical_name, &canonical_type, &canonical_domain, &flags);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ if (isempty(canonical_name))
+ canonical_name = NULL;
+ if (isempty(canonical_type))
+ canonical_type = NULL;
+
+ if (!streq_ptr(name, canonical_name) ||
+ !streq_ptr(type, canonical_type) ||
+ !streq_ptr(domain, canonical_domain)) {
+
+ printf("%*s(", (int) indent, "");
+
+ if (canonical_name)
+ printf("%s/", canonical_name);
+ if (canonical_type)
+ printf("%s/", canonical_type);
+
+ printf("%s)\n", canonical_domain);
+ }
+
+ print_source(flags, ts);
+
+ return 0;
+}
+
static void help_dns_types(void) {
int i;
const char *t;
@@ -464,33 +665,46 @@ static void help_dns_classes(void) {
}
static void help(void) {
- printf("%s [OPTIONS...]\n\n"
- "Resolve IPv4 or IPv6 addresses.\n\n"
- " -h --help Show this help\n"
- " --version Show package version\n"
- " -4 Resolve IPv4 addresses\n"
- " -6 Resolve IPv6 addresses\n"
- " -i INTERFACE Look on interface\n"
- " -p --protocol=PROTOCOL Look via protocol\n"
- " -t --type=TYPE Query RR with DNS type\n"
- " -c --class=CLASS Query RR with DNS class\n"
- " --legend[=BOOL] Do [not] print column headers\n"
- , program_invocation_short_name);
+ printf("%s [OPTIONS...] NAME...\n"
+ "%s [OPTIONS...] --service [[NAME] TYPE] DOMAIN\n\n"
+ "Resolve domain names, IPv4 or IPv6 addresses, resource records, and services.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " -4 Resolve IPv4 addresses\n"
+ " -6 Resolve IPv6 addresses\n"
+ " -i INTERFACE Look on interface\n"
+ " -p --protocol=PROTOCOL Look via protocol\n"
+ " -t --type=TYPE Query RR with DNS type\n"
+ " -c --class=CLASS Query RR with DNS class\n"
+ " --service Resolve service (SRV)\n"
+ " --service-address=BOOL Do [not] resolve address for services\n"
+ " --service-txt=BOOL Do [not] resolve TXT records for services\n"
+ " --cname=BOOL Do [not] follow CNAME redirects\n"
+ " --legend=BOOL Do [not] print column headers\n"
+ , program_invocation_short_name, program_invocation_short_name);
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_LEGEND,
+ ARG_SERVICE,
+ ARG_CNAME,
+ ARG_SERVICE_ADDRESS,
+ ARG_SERVICE_TXT,
};
static const struct option options[] = {
- { "help", no_argument, NULL, 'h' },
- { "version", no_argument, NULL, ARG_VERSION },
- { "type", required_argument, NULL, 't' },
- { "class", required_argument, NULL, 'c' },
- { "legend", optional_argument, NULL, ARG_LEGEND },
- { "protocol", required_argument, NULL, 'p' },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "type", required_argument, NULL, 't' },
+ { "class", required_argument, NULL, 'c' },
+ { "legend", required_argument, NULL, ARG_LEGEND },
+ { "protocol", required_argument, NULL, 'p' },
+ { "cname", required_argument, NULL, ARG_CNAME },
+ { "service", no_argument, NULL, ARG_SERVICE },
+ { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS },
+ { "service-txt", required_argument, NULL, ARG_SERVICE_TXT },
{}
};
@@ -563,16 +777,11 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_LEGEND:
- if (optarg) {
- r = parse_boolean(optarg);
- if (r < 0) {
- log_error("Failed to parse --legend= argument");
- return r;
- }
-
- arg_legend = !!r;
- } else
- arg_legend = false;
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --legend= argument");
+
+ arg_legend = r;
break;
case 'p':
@@ -591,6 +800,40 @@ static int parse_argv(int argc, char *argv[]) {
break;
+ case ARG_SERVICE:
+ arg_resolve_service = true;
+ break;
+
+ case ARG_CNAME:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --cname= argument.");
+ if (r == 0)
+ arg_flags |= SD_RESOLVED_NO_CNAME;
+ else
+ arg_flags &= ~SD_RESOLVED_NO_CNAME;
+ break;
+
+ case ARG_SERVICE_ADDRESS:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --service-address= argument.");
+ if (r == 0)
+ arg_flags |= SD_RESOLVED_NO_ADDRESS;
+ else
+ arg_flags &= ~SD_RESOLVED_NO_ADDRESS;
+ break;
+
+ case ARG_SERVICE_TXT:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --service-txt= argument.");
+ if (r == 0)
+ arg_flags |= SD_RESOLVED_NO_TXT;
+ else
+ arg_flags &= ~SD_RESOLVED_NO_TXT;
+ break;
+
case '?':
return -EINVAL;
@@ -599,7 +842,12 @@ static int parse_argv(int argc, char *argv[]) {
}
if (arg_type == 0 && arg_class != 0) {
- log_error("--class= may only be used in conjunction with --type=");
+ log_error("--class= may only be used in conjunction with --type=.");
+ return -EINVAL;
+ }
+
+ if (arg_type != 0 && arg_resolve_service) {
+ log_error("--service and --type= may not be combined.");
return -EINVAL;
}
@@ -632,6 +880,28 @@ int main(int argc, char **argv) {
goto finish;
}
+ if (arg_resolve_service) {
+
+ if (argc < optind + 1) {
+ log_error("Domain specification required.");
+ r = -EINVAL;
+ goto finish;
+
+ } else if (argc == optind + 1)
+ r = resolve_service(bus, NULL, NULL, argv[optind]);
+ else if (argc == optind + 2)
+ r = resolve_service(bus, NULL, argv[optind], argv[optind+1]);
+ else if (argc == optind + 3)
+ r = resolve_service(bus, argv[optind], argv[optind+1], argv[optind+2]);
+ else {
+ log_error("Too many arguments");
+ r = -EINVAL;
+ goto finish;
+ }
+
+ goto finish;
+ }
+
while (argv[optind]) {
int family, ifindex, k;
union in_addr_union a;
diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c
index f0a3b607d4..1908cae2b7 100644
--- a/src/resolve/resolved-bus.c
+++ b/src/resolve/resolved-bus.c
@@ -31,15 +31,14 @@ static int reply_query_state(DnsQuery *q) {
const char *name;
int r;
- if (q->request_hostname)
- name = q->request_hostname;
- else {
+ if (q->request_address_valid) {
r = in_addr_to_string(q->request_family, &q->request_address, &ip);
if (r < 0)
return r;
name = ip;
- }
+ } else
+ name = dns_question_name(q->question);
switch (q->state) {
@@ -132,10 +131,9 @@ static int append_address(sd_bus_message *reply, DnsResourceRecord *rr, int ifin
}
static void bus_method_resolve_hostname_complete(DnsQuery *q) {
- _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *cname = NULL, *canonical = NULL;
+ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL;
_cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
- _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
- unsigned added = 0, i;
+ unsigned added = 0;
int r;
assert(q);
@@ -145,6 +143,16 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) {
goto finish;
}
+ r = dns_query_process_cname(q);
+ if (r == -ELOOP) {
+ r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_name(q->question));
+ goto finish;
+ }
+ if (r < 0)
+ goto finish;
+ if (r > 0) /* This was a cname, and the query was restarted. */
+ return;
+
r = sd_bus_message_new_method_return(q->request, &reply);
if (r < 0)
goto finish;
@@ -154,92 +162,42 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) {
goto finish;
if (q->answer) {
- answer = dns_answer_ref(q->answer);
+ DnsResourceRecord *rr;
+ int ifindex;
- for (i = 0; i < answer->n_rrs; i++) {
- r = dns_question_matches_rr(q->question, answer->items[i].rr);
+ DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
+ r = dns_question_matches_rr(q->question, rr);
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->items[i].rr);
- if (r < 0)
- goto finish;
- if (r > 0)
- cname = dns_resource_record_ref(answer->items[i].rr);
-
+ if (r == 0)
continue;
- }
- r = append_address(reply, answer->items[i].rr, answer->items[i].ifindex);
+ r = append_address(reply, rr, ifindex);
if (r < 0)
goto finish;
if (!canonical)
- canonical = dns_resource_record_ref(answer->items[i].rr);
+ canonical = dns_resource_record_ref(rr);
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);
- 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->items[i].rr);
- if (r < 0)
- goto finish;
- if (r == 0)
- continue;
-
- r = append_address(reply, answer->items[i].rr, answer->items[i].ifindex);
- if (r < 0)
- goto finish;
-
- if (!canonical)
- canonical = dns_resource_record_ref(answer->items[i].rr);
-
- 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 < 0) {
- r = sd_bus_reply_method_errno(q->request, -r, NULL);
- goto finish;
- }
-
- return;
- }
+ if (added <= 0) {
+ r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_name(q->question));
+ goto finish;
}
r = sd_bus_message_close_container(reply);
if (r < 0)
goto finish;
- /* Return the precise spelling and uppercasing reported by the server */
+ /* Return the precise spelling and uppercasing and CNAME target 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));
+ 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;
@@ -248,23 +206,23 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) {
finish:
if (r < 0) {
log_error_errno(r, "Failed to send hostname reply: %m");
- sd_bus_reply_method_errno(q->request, -r, NULL);
+ 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) {
+static int check_ifindex_flags(int ifindex, uint64_t *flags, uint64_t ok, sd_bus_error *error) {
assert(flags);
if (ifindex < 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index");
- if (*flags & ~SD_RESOLVED_FLAGS_ALL)
+ if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_CNAME|ok))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter");
- if (*flags == 0)
- *flags = SD_RESOLVED_FLAGS_DEFAULT;
+ if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */
+ *flags |= SD_RESOLVED_PROTOCOLS_ALL;
return 0;
}
@@ -281,6 +239,8 @@ static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata,
assert(message);
assert(m);
+ assert_cc(sizeof(int) == sizeof(int32_t));
+
r = sd_bus_message_read(message, "isit", &ifindex, &hostname, &family, &flags);
if (r < 0)
return r;
@@ -288,41 +248,19 @@ static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata,
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);
+ r = dns_name_is_valid(hostname);
if (r < 0)
+ return r;
+ if (r == 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", hostname);
- r = check_ifindex_flags(ifindex, &flags, error);
+ r = check_ifindex_flags(ifindex, &flags, 0, 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_question_new_address(&question, family, hostname);
+ if (r < 0)
+ return r;
r = dns_query_new(m, &q, question, ifindex, flags);
if (r < 0)
@@ -330,27 +268,28 @@ static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata,
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, message);
if (r < 0)
- return r;
+ goto fail;
r = dns_query_go(q);
- if (r < 0) {
- dns_query_free(q);
- return r;
- }
+ if (r < 0)
+ goto fail;
return 1;
+
+fail:
+ dns_query_free(q);
+ return r;
}
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;
+ DnsResourceRecord *rr;
+ unsigned added = 0;
+ int ifindex, r;
assert(q);
@@ -359,6 +298,8 @@ static void bus_method_resolve_address_complete(DnsQuery *q) {
goto finish;
}
+ /* We don't process CNAME for PTR lookups. */
+
r = sd_bus_message_new_method_return(q->request, &reply);
if (r < 0)
goto finish;
@@ -368,16 +309,14 @@ static void bus_method_resolve_address_complete(DnsQuery *q) {
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->items[i].rr);
+ DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
+ r = dns_question_matches_rr(q->question, rr);
if (r < 0)
goto finish;
if (r == 0)
continue;
- r = sd_bus_message_append(reply, "(is)", answer->items[i].ifindex, answer->items[i].rr->ptr.name);
+ r = sd_bus_message_append(reply, "(is)", ifindex, rr->ptr.name);
if (r < 0)
goto finish;
@@ -385,12 +324,11 @@ static void bus_method_resolve_address_complete(DnsQuery *q) {
}
}
- if (added == 0) {
+ 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);
+ r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Address '%s' does not have any RR of requested type", strna(ip));
goto finish;
}
@@ -407,16 +345,14 @@ static void bus_method_resolve_address_complete(DnsQuery *q) {
finish:
if (r < 0) {
log_error_errno(r, "Failed to send address reply: %m");
- sd_bus_reply_method_errno(q->request, -r, NULL);
+ sd_bus_reply_method_errno(q->request, r, NULL);
}
dns_query_free(q);
}
static int bus_method_resolve_address(sd_bus_message *message, void *userdata, sd_bus_error *error) {
- _cleanup_(dns_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;
@@ -428,6 +364,8 @@ static int bus_method_resolve_address(sd_bus_message *message, void *userdata, s
assert(message);
assert(m);
+ assert_cc(sizeof(int) == sizeof(int32_t));
+
r = sd_bus_message_read(message, "ii", &ifindex, &family);
if (r < 0)
return r;
@@ -446,25 +384,11 @@ static int bus_method_resolve_address(sd_bus_message *message, void *userdata, s
if (r < 0)
return r;
- r = check_ifindex_flags(ifindex, &flags, error);
- if (r < 0)
- return r;
-
- r = dns_name_reverse(family, d, &reverse);
+ r = check_ifindex_flags(ifindex, &flags, 0, error);
if (r < 0)
return r;
- question = dns_question_new(1);
- if (!question)
- return -ENOMEM;
-
- key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, reverse);
- if (!key)
- return -ENOMEM;
-
- reverse = NULL;
-
- r = dns_question_add(question, key);
+ r = dns_question_new_reverse(&question, family, d);
if (r < 0)
return r;
@@ -479,21 +403,58 @@ static int bus_method_resolve_address(sd_bus_message *message, void *userdata, s
r = dns_query_bus_track(q, message);
if (r < 0)
- return r;
+ goto fail;
r = dns_query_go(q);
- if (r < 0) {
- dns_query_free(q);
- return r;
- }
+ if (r < 0)
+ goto fail;
return 1;
+
+fail:
+ dns_query_free(q);
+ return r;
+}
+
+static int bus_message_append_rr(sd_bus_message *m, DnsResourceRecord *rr, int ifindex) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+ size_t start;
+ int r;
+
+ assert(m);
+ assert(rr);
+
+ r = sd_bus_message_open_container(m, 'r', "iqqay");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(m, "iqq",
+ ifindex,
+ rr->key->class,
+ rr->key->type);
+ if (r < 0)
+ return r;
+
+ r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0);
+ if (r < 0)
+ return r;
+
+ p->refuse_compression = true;
+
+ r = dns_packet_append_rr(p, rr, &start);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append_array(m, 'y', DNS_PACKET_DATA(p) + start, p->size - start);
+ if (r < 0)
+ return r;
+
+ return sd_bus_message_close_container(m);
}
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;
+ unsigned added = 0;
int r;
assert(q);
@@ -503,6 +464,16 @@ static void bus_method_resolve_record_complete(DnsQuery *q) {
goto finish;
}
+ r = dns_query_process_cname(q);
+ if (r == -ELOOP) {
+ r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_name(q->question));
+ goto finish;
+ }
+ if (r < 0)
+ goto finish;
+ if (r > 0) /* Following a CNAME */
+ return;
+
r = sd_bus_message_new_method_return(q->request, &reply);
if (r < 0)
goto finish;
@@ -512,44 +483,17 @@ static void bus_method_resolve_record_complete(DnsQuery *q) {
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;
+ DnsResourceRecord *rr;
+ int ifindex;
- r = dns_question_matches_rr(q->question, answer->items[i].rr);
+ DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
+ r = dns_question_matches_rr(q->question, rr);
if (r < 0)
goto finish;
if (r == 0)
continue;
- r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0);
- if (r < 0)
- goto finish;
-
- p->refuse_compression = true;
-
- r = dns_packet_append_rr(p, answer->items[i].rr, &start);
- if (r < 0)
- goto finish;
-
- r = sd_bus_message_open_container(reply, 'r', "iqqay");
- if (r < 0)
- goto finish;
-
- r = sd_bus_message_append(reply, "iqq",
- answer->items[i].ifindex,
- answer->items[i].rr->key->class,
- answer->items[i].rr->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);
+ r = bus_message_append_rr(reply, rr, ifindex);
if (r < 0)
goto finish;
@@ -558,7 +502,7 @@ static void bus_method_resolve_record_complete(DnsQuery *q) {
}
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);
+ r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Name '%s' does not have any RR of the requested type", dns_question_name(q->question));
goto finish;
}
@@ -575,7 +519,7 @@ static void bus_method_resolve_record_complete(DnsQuery *q) {
finish:
if (r < 0) {
log_error_errno(r, "Failed to send record reply: %m");
- sd_bus_reply_method_errno(q->request, -r, NULL);
+ sd_bus_reply_method_errno(q->request, r, NULL);
}
dns_query_free(q);
@@ -594,15 +538,19 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd
assert(message);
assert(m);
+ assert_cc(sizeof(int) == sizeof(int32_t));
+
r = sd_bus_message_read(message, "isqqt", &ifindex, &name, &class, &type, &flags);
if (r < 0)
return r;
- r = dns_name_normalize(name, NULL);
+ r = dns_name_is_valid(name);
if (r < 0)
+ return r;
+ if (r == 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid name '%s'", name);
- r = check_ifindex_flags(ifindex, &flags, error);
+ r = check_ifindex_flags(ifindex, &flags, 0, error);
if (r < 0)
return r;
@@ -623,20 +571,535 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd
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, message);
if (r < 0)
- return r;
+ goto fail;
r = dns_query_go(q);
+ if (r < 0)
+ goto fail;
+
+ return 1;
+
+fail:
+ dns_query_free(q);
+ return r;
+}
+
+static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr) {
+ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL;
+ DnsQuery *aux;
+ int r;
+
+ assert(q);
+ assert(reply);
+ assert(rr);
+ assert(rr->key);
+
+ if (rr->key->type != DNS_TYPE_SRV)
+ return 0;
+
+ if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
+ /* First, let's see if we could find an appropriate A or AAAA
+ * record for the SRV record */
+ LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) {
+ DnsResourceRecord *zz;
+
+ if (aux->state != DNS_TRANSACTION_SUCCESS)
+ continue;
+ if (aux->auxiliary_result != 0)
+ continue;
+
+ r = dns_name_equal(dns_question_name(aux->question), rr->srv.name);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ DNS_ANSWER_FOREACH(zz, aux->answer) {
+
+ r = dns_question_matches_rr(aux->question, zz);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ canonical = dns_resource_record_ref(zz);
+ break;
+ }
+
+ if (canonical)
+ break;
+ }
+
+ /* Is there are successful A/AAAA lookup for this SRV RR? If not, don't add it */
+ if (!canonical)
+ return 0;
+ }
+
+ r = sd_bus_message_open_container(reply, 'r', "qqqsa(iiay)s");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(
+ reply,
+ "qqqs",
+ rr->srv.priority, rr->srv.weight, rr->srv.port, rr->srv.name);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(reply, 'a', "(iiay)");
+ if (r < 0)
+ return r;
+
+ if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
+ LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) {
+ DnsResourceRecord *zz;
+ int ifindex;
+
+ if (aux->state != DNS_TRANSACTION_SUCCESS)
+ continue;
+ if (aux->auxiliary_result != 0)
+ continue;
+
+ r = dns_name_equal(dns_question_name(aux->question), rr->srv.name);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ DNS_ANSWER_FOREACH_IFINDEX(zz, ifindex, aux->answer) {
+
+ r = dns_question_matches_rr(aux->question, zz);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ r = append_address(reply, zz, ifindex);
+ if (r < 0)
+ return r;
+ }
+ }
+ }
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+
+ /* Note that above we appended the hostname as encoded in the
+ * SRV, and here the canonical hostname this maps to. */
+ r = sd_bus_message_append(reply, "s", canonical ? DNS_RESOURCE_KEY_NAME(canonical->key) : rr->srv.name);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+static int append_txt(sd_bus_message *reply, DnsResourceRecord *rr) {
+ DnsTxtItem *i;
+ int r;
+
+ assert(reply);
+ assert(rr);
+ assert(rr->key);
+
+ if (rr->key->type != DNS_TYPE_TXT)
+ return 0;
+
+ LIST_FOREACH(items, i, rr->txt.items) {
+
+ if (i->length <= 0)
+ continue;
+
+ r = sd_bus_message_append_array(reply, 'y', i->data, i->length);
+ if (r < 0)
+ return r;
+ }
+
+ return 1;
+}
+
+static void resolve_service_all_complete(DnsQuery *q) {
+ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL;
+ DnsQuery *aux;
+ unsigned added = false;
+ int r;
+
+ assert(q);
+
+ if (q->block_all_complete > 0)
+ return;
+
+ if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
+ DnsQuery *bad = NULL;
+ bool have_success = false;
+
+ LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) {
+
+ switch (aux->state) {
+
+ case DNS_TRANSACTION_PENDING:
+ /* If an auxiliary query is still pending, let's wait */
+ return;
+
+ case DNS_TRANSACTION_SUCCESS:
+ if (aux->auxiliary_result == 0)
+ have_success = true;
+ else
+ bad = aux;
+ break;
+
+ default:
+ bad = aux;
+ break;
+ }
+ }
+
+ if (!have_success) {
+ /* We can only return one error, hence pick the last error we encountered */
+
+ assert(bad);
+
+ if (bad->state == DNS_TRANSACTION_SUCCESS) {
+ assert(bad->auxiliary_result != 0);
+
+ if (bad->auxiliary_result == -ELOOP) {
+ r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_name(bad->question));
+ goto finish;
+ }
+
+ r = bad->auxiliary_result;
+ goto finish;
+ }
+
+ r = reply_query_state(bad);
+ goto finish;
+ }
+ }
+
+ r = sd_bus_message_new_method_return(q->request, &reply);
+ if (r < 0)
+ goto finish;
+
+ r = sd_bus_message_open_container(reply, 'a', "(qqqsa(iiay)s)");
+ if (r < 0)
+ goto finish;
+
+ if (q->answer) {
+ DnsResourceRecord *rr;
+
+ DNS_ANSWER_FOREACH(rr, q->answer) {
+ r = dns_question_matches_rr(q->question, rr);
+ if (r < 0)
+ goto finish;
+ if (r == 0)
+ continue;
+
+ r = append_srv(q, reply, rr);
+ if (r < 0)
+ goto finish;
+ if (r == 0) /* not an SRV record */
+ continue;
+
+ if (!canonical)
+ canonical = dns_resource_record_ref(rr);
+
+ added++;
+ }
+ }
+
+ if (added <= 0) {
+ r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_name(q->question));
+ goto finish;
+ }
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ goto finish;
+
+ r = sd_bus_message_open_container(reply, 'a', "ay");
+ if (r < 0)
+ goto finish;
+
+ if (q->answer) {
+ DnsResourceRecord *rr;
+
+ DNS_ANSWER_FOREACH(rr, q->answer) {
+ r = dns_question_matches_rr(q->question, rr);
+ if (r < 0)
+ goto finish;
+ if (r == 0)
+ continue;
+
+ r = append_txt(reply, rr);
+ if (r < 0)
+ goto finish;
+ }
+ }
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ goto finish;
+
+ assert(canonical);
+ r = dns_service_split(DNS_RESOURCE_KEY_NAME(canonical->key), &name, &type, &domain);
+ if (r < 0)
+ goto finish;
+
+ r = sd_bus_message_append(
+ reply,
+ "ssst",
+ name, type, domain,
+ SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family));
+ if (r < 0)
+ goto finish;
+
+ r = sd_bus_send(q->manager->bus, reply, NULL);
+
+finish:
+ if (r < 0) {
+ log_error_errno(r, "Failed to send service reply: %m");
+ sd_bus_reply_method_errno(q->request, r, NULL);
+ }
+
+ dns_query_free(q);
+}
+
+static void resolve_service_hostname_complete(DnsQuery *q) {
+ int r;
+
+ assert(q);
+ assert(q->auxiliary_for);
+
+ if (q->state != DNS_TRANSACTION_SUCCESS) {
+ resolve_service_all_complete(q->auxiliary_for);
+ return;
+ }
+
+ r = dns_query_process_cname(q);
+ if (r > 0) /* This was a cname, and the query was restarted. */
+ return;
+
+ /* This auxiliary lookup is finished or failed, let's see if all are finished now. */
+ q->auxiliary_result = r;
+ resolve_service_all_complete(q->auxiliary_for);
+}
+
+static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifindex) {
+ _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
+ DnsQuery *aux;
+ int r;
+
+ assert(q);
+ assert(rr);
+ assert(rr->key);
+ assert(rr->key->type == DNS_TYPE_SRV);
+
+ /* OK, we found an SRV record for the service. Let's resolve
+ * the hostname included in it */
+
+ r = dns_question_new_address(&question, q->request_family, rr->srv.name);
+ if (r < 0)
+ return r;
+
+ r = dns_query_new(q->manager, &aux, question, ifindex, q->flags);
+ if (r < 0)
+ return r;
+
+ aux->request_family = q->request_family;
+ aux->complete = resolve_service_hostname_complete;
+
+ r = dns_query_make_auxiliary(aux, q);
+ if (r == -EAGAIN) {
+ /* Too many auxiliary lookups? If so, don't complain,
+ * let's just not add this one, we already have more
+ * than enough */
+
+ dns_query_free(aux);
+ return 0;
+ }
+ if (r < 0)
+ goto fail;
+
+ /* Note that auxiliary queries do not track the original bus
+ * client, only the primary request does that. */
+
+ r = dns_query_go(aux);
+ if (r < 0)
+ goto fail;
+
+ return 1;
+
+fail:
+ dns_query_free(aux);
+ return r;
+}
+
+static void bus_method_resolve_service_complete(DnsQuery *q) {
+ unsigned found = 0;
+ int r;
+
+ assert(q);
+
+ if (q->state != DNS_TRANSACTION_SUCCESS) {
+ r = reply_query_state(q);
+ goto finish;
+ }
+
+ r = dns_query_process_cname(q);
+ if (r == -ELOOP) {
+ r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_name(q->question));
+ goto finish;
+ }
+ if (r < 0)
+ goto finish;
+ if (r > 0) /* This was a cname, and the query was restarted. */
+ return;
+
+ if (q->answer) {
+ DnsResourceRecord *rr;
+ int ifindex;
+
+ DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
+ r = dns_question_matches_rr(q->question, rr);
+ if (r < 0)
+ goto finish;
+ if (r == 0)
+ continue;
+
+ if (rr->key->type != DNS_TYPE_SRV)
+ continue;
+
+ if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
+ q->block_all_complete ++;
+ r = resolve_service_hostname(q, rr, ifindex);
+ q->block_all_complete --;
+
+ if (r < 0)
+ goto finish;
+ }
+
+ found++;
+ }
+ }
+
+ if (found <= 0) {
+ r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_name(q->question));
+ goto finish;
+ }
+
+ /* Maybe we are already finished? check now... */
+ resolve_service_all_complete(q);
+ return;
+
+finish:
if (r < 0) {
- dns_query_free(q);
+ log_error_errno(r, "Failed to send service reply: %m");
+ sd_bus_reply_method_errno(q->request, r, NULL);
+ }
+
+ dns_query_free(q);
+}
+
+static int bus_method_resolve_service(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
+ _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
+ const char *name, *type, *domain, *joined;
+ _cleanup_free_ char *n = NULL;
+ Manager *m = userdata;
+ int family, ifindex;
+ uint64_t flags;
+ DnsQuery *q;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ assert_cc(sizeof(int) == sizeof(int32_t));
+
+ r = sd_bus_message_read(message, "isssit", &ifindex, &name, &type, &domain, &family, &flags);
+ if (r < 0)
return r;
+
+ if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family);
+
+ if (isempty(name))
+ name = NULL;
+ else {
+ if (!dns_service_name_is_valid(name))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid service name '%s'", name);
}
+ if (isempty(type))
+ type = NULL;
+ else {
+ r = dns_srv_type_verify(type);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid SRV service type '%s'", type);
+ }
+
+ r = dns_name_is_valid(domain);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid domain '%s'", domain);
+
+ if (name && !type)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Service name cannot be specified without service type.");
+
+ r = check_ifindex_flags(ifindex, &flags, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS, error);
+ if (r < 0)
+ return r;
+
+ if (type) {
+ /* If the type is specified, we generate the full domain name to look up ourselves */
+ r = dns_service_join(name, type, domain, &n);
+ if (r < 0)
+ return r;
+
+ joined = n;
+ } else
+ /* If no type is specified, we assume the domain
+ * contains the full domain name to lookup already */
+ joined = domain;
+
+ r = dns_question_new_service(&question, joined, !(flags & SD_RESOLVED_NO_TXT));
+ 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->complete = bus_method_resolve_service_complete;
+
+ r = dns_query_bus_track(q, message);
+ if (r < 0)
+ goto fail;
+
+ r = dns_query_go(q);
+ if (r < 0)
+ goto fail;
+
return 1;
+
+fail:
+ dns_query_free(q);
+ return r;
}
static const sd_bus_vtable resolve_vtable[] = {
@@ -644,6 +1107,7 @@ static const sd_bus_vtable resolve_vtable[] = {
SD_BUS_METHOD("ResolveHostname", "isit", "a(iiay)st", bus_method_resolve_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ResolveAddress", "iiayt", "a(is)t", bus_method_resolve_address, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ResolveRecord", "isqqt", "a(iqqay)t", bus_method_resolve_record, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("ResolveService", "isssit", "a(qqqsa(iiay)s)aayssst", bus_method_resolve_service, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END,
};
diff --git a/src/resolve/resolved-def.h b/src/resolve/resolved-def.h
index 086d111205..85af1923ff 100644
--- a/src/resolve/resolved-def.h
+++ b/src/resolve/resolved-def.h
@@ -24,7 +24,9 @@
#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_NO_CNAME ((uint64_t) 8)
+#define SD_RESOLVED_NO_TXT ((uint64_t) 16)
+#define SD_RESOLVED_NO_ADDRESS ((uint64_t) 32)
-#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
+#define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6)
+#define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_LLMNR|SD_RESOLVED_DNS)
diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h
index 044d73b19c..b5b1ad56ba 100644
--- a/src/resolve/resolved-dns-answer.h
+++ b/src/resolve/resolved-dns-answer.h
@@ -58,3 +58,20 @@ void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local);
int dns_answer_reserve(DnsAnswer **a, unsigned n_free);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref);
+
+#define DNS_ANSWER_FOREACH(kk, a) \
+ for (unsigned _i = ({ \
+ (kk) = ((a) && (a)->n_rrs > 0 ? (a)->items[0].rr : NULL); \
+ 0; \
+ }); \
+ (a) && ((_i) < (a)->n_rrs); \
+ _i++, (kk) = (_i < (a)->n_rrs ? (a)->items[_i].rr : NULL))
+
+#define DNS_ANSWER_FOREACH_IFINDEX(kk, ifindex, a) \
+ for (unsigned _i = ({ \
+ (kk) = ((a) && (a)->n_rrs > 0 ? (a)->items[0].rr : NULL); \
+ (ifindex) = ((a) && (a)->n_rrs > 0 ? (a)->items[0].ifindex : 0); \
+ 0; \
+ }); \
+ (a) && ((_i) < (a)->n_rrs); \
+ _i++, (kk) = (_i < (a)->n_rrs ? (a)->items[_i].rr : NULL), (ifindex) = (_i < (a)->n_rrs ? (a)->items[_i].ifindex : 0))
diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c
index f23b3cf893..472486777c 100644
--- a/src/resolve/resolved-dns-packet.c
+++ b/src/resolve/resolved-dns-packet.c
@@ -370,6 +370,28 @@ int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start) {
return 0;
}
+int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start) {
+ void *d;
+ int r;
+
+ assert(p);
+ assert(s || size == 0);
+
+ if (size > 255)
+ return -E2BIG;
+
+ r = dns_packet_extend(p, 1 + size, &d, start);
+ if (r < 0)
+ return r;
+
+ ((uint8_t*) d)[0] = (uint8_t) size;
+
+ if (size > 0)
+ memcpy(((uint8_t*) d) + 1, s, size);
+
+ return 0;
+}
+
int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start) {
void *w;
int r;
@@ -643,19 +665,20 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
break;
case DNS_TYPE_SPF: /* exactly the same as TXT */
- case DNS_TYPE_TXT: {
- char **s;
+ case DNS_TYPE_TXT:
- if (strv_isempty(rr->txt.strings)) {
+ if (!rr->txt.items) {
/* RFC 6763, section 6.1 suggests to generate
* single empty string for an empty array. */
- r = dns_packet_append_string(p, "", NULL);
+ r = dns_packet_append_raw_string(p, NULL, 0, NULL);
if (r < 0)
goto fail;
} else {
- STRV_FOREACH(s, rr->txt.strings) {
- r = dns_packet_append_string(p, *s, NULL);
+ DnsTxtItem *i;
+
+ LIST_FOREACH(items, i, rr->txt.items) {
+ r = dns_packet_append_raw_string(p, i->data, i->length, NULL);
if (r < 0)
goto fail;
}
@@ -663,7 +686,6 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
r = 0;
break;
- }
case DNS_TYPE_A:
r = dns_packet_append_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL);
@@ -1062,6 +1084,35 @@ fail:
return r;
}
+int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start) {
+ size_t saved_rindex;
+ 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, ret, NULL);
+ if (r < 0)
+ goto fail;
+
+ if (size)
+ *size = c;
+ 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,
@@ -1412,24 +1463,37 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
case DNS_TYPE_SPF: /* exactly the same as TXT */
case DNS_TYPE_TXT:
if (rdlength <= 0) {
+ DnsTxtItem *i;
/* RFC 6763, section 6.1 suggests to treat
* empty TXT RRs as equivalent to a TXT record
* with a single empty string. */
- r = strv_extend(&rr->txt.strings, "");
- if (r < 0)
- goto fail;
+ i = malloc0(offsetof(DnsTxtItem, data) + 1); /* for safety reasons we add an extra NUL byte */
+ if (!i)
+ return -ENOMEM;
+
+ rr->txt.items = i;
} else {
+ DnsTxtItem *last = NULL;
+
while (p->rindex < offset + rdlength) {
- char *s;
+ DnsTxtItem *i;
+ const void *data;
+ size_t sz;
- r = dns_packet_read_string(p, &s, NULL);
+ r = dns_packet_read_raw_string(p, &data, &sz, NULL);
if (r < 0)
- goto fail;
+ return r;
- r = strv_consume(&rr->txt.strings, s);
- if (r < 0)
- goto fail;
+ i = malloc0(offsetof(DnsTxtItem, data) + sz + 1); /* extra NUL byte at the end */
+ if (!i)
+ return -ENOMEM;
+
+ memcpy(i->data, data, sz);
+ i->length = sz;
+
+ LIST_INSERT_AFTER(items, rr->txt.items, last, i);
+ last = i;
}
}
diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h
index 73803e4e82..48df5dfc53 100644
--- a/src/resolve/resolved-dns-packet.h
+++ b/src/resolve/resolved-dns-packet.h
@@ -155,9 +155,9 @@ int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start);
int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start);
int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start);
int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start);
+int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start);
int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, size_t *start);
-int dns_packet_append_name(DnsPacket *p, const char *name,
- bool allow_compression, size_t *start);
+int dns_packet_append_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);
@@ -167,8 +167,8 @@ 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_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start);
+int dns_packet_read_name(DnsPacket *p, char **ret, bool allow_compression, size_t *start);
int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start);
int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start);
diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c
index f7cb84e2a6..c1cd650651 100644
--- a/src/resolve/resolved-dns-query.c
+++ b/src/resolve/resolved-dns-query.c
@@ -30,6 +30,7 @@
#define CNAME_MAX 8
#define QUERIES_MAX 2048
+#define AUXILIARY_QUERIES_MAX 64
static void dns_query_stop(DnsQuery *q) {
DnsTransaction *t;
@@ -48,6 +49,15 @@ DnsQuery *dns_query_free(DnsQuery *q) {
if (!q)
return NULL;
+ while (q->auxiliary_queries)
+ dns_query_free(q->auxiliary_queries);
+
+ if (q->auxiliary_for) {
+ assert(q->auxiliary_for->n_auxiliary_queries > 0);
+ q->auxiliary_for->n_auxiliary_queries--;
+ LIST_REMOVE(auxiliary_queries, q->auxiliary_for->auxiliary_queries, q);
+ }
+
dns_query_stop(q);
set_free(q->transactions);
@@ -111,6 +121,29 @@ int dns_query_new(Manager *m, DnsQuery **ret, DnsQuestion *question, int ifindex
return 0;
}
+int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for) {
+ assert(q);
+ assert(auxiliary_for);
+
+ /* Ensure that that the query is not auxiliary yet, and
+ * nothing else is auxiliary to it either */
+ assert(!q->auxiliary_for);
+ assert(!q->auxiliary_queries);
+
+ /* Ensure that the unit we shall be made auxiliary for isn't
+ * auxiliary itself */
+ assert(!auxiliary_for->auxiliary_for);
+
+ if (auxiliary_for->n_auxiliary_queries >= AUXILIARY_QUERIES_MAX)
+ return -EAGAIN;
+
+ LIST_PREPEND(auxiliary_queries, auxiliary_for->auxiliary_queries, q);
+ q->auxiliary_for = auxiliary_for;
+
+ auxiliary_for->n_auxiliary_queries++;
+ return 0;
+}
+
static void dns_query_complete(DnsQuery *q, DnsTransactionState state) {
assert(q);
assert(!IN_SET(state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING));
@@ -622,7 +655,7 @@ int dns_query_go(DnsQuery *q) {
assert(q->question);
assert(q->question->n_keys > 0);
- name = DNS_RESOURCE_KEY_NAME(q->question->keys[0]);
+ name = dns_question_name(q->question);
LIST_FOREACH(scopes, s, q->manager->dns_scopes) {
DnsScopeMatch match;
@@ -831,12 +864,13 @@ void dns_query_ready(DnsQuery *q) {
dns_query_complete(q, state);
}
-int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) {
+static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) {
_cleanup_(dns_question_unrefp) DnsQuestion *nq = NULL;
int r;
assert(q);
+ q->n_cname_redirects ++;
if (q->n_cname_redirects > CNAME_MAX)
return -ELOOP;
@@ -848,14 +882,66 @@ int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) {
q->question = nq;
nq = NULL;
- q->n_cname_redirects++;
-
dns_query_stop(q);
q->state = DNS_TRANSACTION_NULL;
return 0;
}
+int dns_query_process_cname(DnsQuery *q) {
+ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *cname = NULL;
+ DnsResourceRecord *rr;
+ int r;
+
+ assert(q);
+
+ if (q->state != DNS_TRANSACTION_SUCCESS)
+ return 0;
+
+ DNS_ANSWER_FOREACH(rr, q->answer) {
+
+ r = dns_question_matches_rr(q->question, rr);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return 0; /* The answer matches directly, no need to follow cnames */
+
+ r = dns_question_matches_cname(q->question, rr);
+ if (r < 0)
+ return r;
+ if (r > 0 && !cname)
+ cname = dns_resource_record_ref(rr);
+ }
+
+ if (!cname)
+ return 0; /* No cname to follow */
+
+ if (q->flags & SD_RESOLVED_NO_CNAME)
+ return -ELOOP;
+
+ /* OK, let's actually follow the CNAME */
+ r = dns_query_cname_redirect(q, cname);
+ if (r < 0)
+ return r;
+
+ /* Let's see if the answer can already answer the new
+ * redirected question */
+ DNS_ANSWER_FOREACH(rr, q->answer) {
+ r = dns_question_matches_rr(q->question, rr);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return 0; /* It can answer it, yay! */
+ }
+
+ /* OK, it cannot, let's begin with the new query */
+ r = dns_query_go(q);
+ if (r < 0)
+ return r;
+
+ return 1; /* We return > 0, if we restarted the query for a new cname */
+}
+
static int on_bus_track(sd_bus_track *t, void *userdata) {
DnsQuery *q = userdata;
diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h
index 2c28a7e284..256dddc00b 100644
--- a/src/resolve/resolved-dns-query.h
+++ b/src/resolve/resolved-dns-query.h
@@ -34,6 +34,16 @@ typedef struct DnsQuery DnsQuery;
struct DnsQuery {
Manager *manager;
+
+ /* When resolving a service, we first create a TXT+SRV query,
+ * and then for the hostnames we discover auxiliary A+AAAA
+ * queries. This pointer always points from the auxiliary
+ * queries back to the TXT+SRV query. */
+ DnsQuery *auxiliary_for;
+ LIST_HEAD(DnsQuery, auxiliary_queries);
+ unsigned n_auxiliary_queries;
+ int auxiliary_result;
+
DnsQuestion *question;
uint64_t flags;
@@ -53,8 +63,9 @@ struct DnsQuery {
/* Bus client information */
sd_bus_message *request;
int request_family;
- const char *request_hostname;
+ bool request_address_valid;
union in_addr_union request_address;
+ unsigned block_all_complete;
/* Completion callback */
void (*complete)(DnsQuery* q);
@@ -65,15 +76,18 @@ struct DnsQuery {
sd_bus_track *bus_track;
LIST_FIELDS(DnsQuery, queries);
+ LIST_FIELDS(DnsQuery, auxiliary_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_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for);
+
int dns_query_go(DnsQuery *q);
void dns_query_ready(DnsQuery *q);
-int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname);
+int dns_query_process_cname(DnsQuery *q);
int dns_query_bus_track(DnsQuery *q, sd_bus_message *m);
diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c
index 48951221dc..868658652d 100644
--- a/src/resolve/resolved-dns-question.c
+++ b/src/resolve/resolved-dns-question.c
@@ -301,3 +301,129 @@ int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname,
return 1;
}
+
+const char *dns_question_name(DnsQuestion *q) {
+ assert(q);
+
+ if (q->n_keys < 1)
+ return NULL;
+
+ return DNS_RESOURCE_KEY_NAME(q->keys[0]);
+}
+
+int dns_question_new_address(DnsQuestion **ret, int family, const char *name) {
+ _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL;
+ int r;
+
+ assert(ret);
+ assert(name);
+
+ if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC))
+ return -EAFNOSUPPORT;
+
+ q = dns_question_new(family == AF_UNSPEC ? 2 : 1);
+ if (!q)
+ return -ENOMEM;
+
+ if (family != AF_INET6) {
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
+
+ key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, name);
+ if (!key)
+ return -ENOMEM;
+
+ r = dns_question_add(q, key);
+ if (r < 0)
+ return r;
+ }
+
+ if (family != AF_INET) {
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
+
+ key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, name);
+ if (!key)
+ return -ENOMEM;
+
+ r = dns_question_add(q, key);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = q;
+ q = NULL;
+
+ return 0;
+}
+
+int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a) {
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
+ _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL;
+ _cleanup_free_ char *reverse = NULL;
+ int r;
+
+ assert(ret);
+ assert(a);
+
+ if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC))
+ return -EAFNOSUPPORT;
+
+ r = dns_name_reverse(family, a, &reverse);
+ if (r < 0)
+ return r;
+
+ q = dns_question_new(1);
+ if (!q)
+ return -ENOMEM;
+
+ key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, reverse);
+ if (!key)
+ return -ENOMEM;
+
+ reverse = NULL;
+
+ r = dns_question_add(q, key);
+ if (r < 0)
+ return r;
+
+ *ret = q;
+ q = NULL;
+
+ return 0;
+}
+
+int dns_question_new_service(DnsQuestion **ret, const char *name, bool with_txt) {
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
+ _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL;
+ int r;
+
+ assert(ret);
+ assert(name);
+
+ q = dns_question_new(1 + with_txt);
+ if (!q)
+ return -ENOMEM;
+
+ key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_SRV, name);
+ if (!key)
+ return -ENOMEM;
+
+ r = dns_question_add(q, key);
+ if (r < 0)
+ return r;
+
+ if (with_txt) {
+ dns_resource_key_unref(key);
+ key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_TXT, name);
+ if (!key)
+ return -ENOMEM;
+
+ r = dns_question_add(q, key);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = q;
+ q = NULL;
+
+ return 0;
+}
diff --git a/src/resolve/resolved-dns-question.h b/src/resolve/resolved-dns-question.h
index 13cd1f20f3..7f65e47158 100644
--- a/src/resolve/resolved-dns-question.h
+++ b/src/resolve/resolved-dns-question.h
@@ -25,7 +25,7 @@ typedef struct DnsQuestion DnsQuestion;
#include "resolved-dns-rr.h"
-/* A simple array of resources keys */
+/* A simple array of resources keys, all sharing the same domain */
struct DnsQuestion {
unsigned n_ref;
@@ -37,6 +37,10 @@ DnsQuestion *dns_question_new(unsigned n);
DnsQuestion *dns_question_ref(DnsQuestion *q);
DnsQuestion *dns_question_unref(DnsQuestion *q);
+int dns_question_new_address(DnsQuestion **ret, int family, const char *name);
+int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a);
+int dns_question_new_service(DnsQuestion **ret, const char *name, bool with_txt);
+
int dns_question_add(DnsQuestion *q, DnsResourceKey *key);
int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr);
@@ -48,4 +52,6 @@ int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b);
int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret);
+const char *dns_question_name(DnsQuestion *q);
+
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuestion*, dns_question_unref);
diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c
index ba2ea686f3..636e07dea9 100644
--- a/src/resolve/resolved-dns-rr.c
+++ b/src/resolve/resolved-dns-rr.c
@@ -273,7 +273,7 @@ DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr) {
case DNS_TYPE_TXT:
case DNS_TYPE_SPF:
- strv_free(rr->txt.strings);
+ dns_txt_item_free_all(rr->txt.items);
break;
case DNS_TYPE_SOA:
@@ -430,7 +430,7 @@ int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecor
case DNS_TYPE_SPF: /* exactly the same as TXT */
case DNS_TYPE_TXT:
- return strv_equal(a->txt.strings, b->txt.strings);
+ return dns_txt_item_equal(a->txt.items, b->txt.items);
case DNS_TYPE_A:
return memcmp(&a->a.in_addr, &b->a.in_addr, sizeof(struct in_addr)) == 0;
@@ -600,6 +600,43 @@ static char *format_types(Bitmap *types) {
return strjoin("( ", str, " )", NULL);
}
+static char *format_txt(DnsTxtItem *first) {
+ DnsTxtItem *i;
+ size_t c = 1;
+ char *p, *s;
+
+ LIST_FOREACH(items, i, first)
+ c += i->length * 4 + 3;
+
+ p = s = new(char, c);
+ if (!s)
+ return NULL;
+
+ LIST_FOREACH(items, i, first) {
+ size_t j;
+
+ if (i != first)
+ *(p++) = ' ';
+
+ *(p++) = '"';
+
+ for (j = 0; j < i->length; j++) {
+ if (i->data[j] < ' ' || i->data[j] == '"' || i->data[j] >= 127) {
+ *(p++) = '\\';
+ *(p++) = '0' + (i->data[j] / 100);
+ *(p++) = '0' + ((i->data[j] / 10) % 10);
+ *(p++) = '0' + (i->data[j] % 10);
+ } else
+ *(p++) = i->data[j];
+ }
+
+ *(p++) = '"';
+ }
+
+ *p = 0;
+ return s;
+}
+
int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
_cleanup_free_ char *k = NULL, *t = NULL;
char *s;
@@ -642,14 +679,13 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) {
case DNS_TYPE_SPF: /* exactly the same as TXT */
case DNS_TYPE_TXT:
- t = strv_join_quoted(rr->txt.strings);
+ t = format_txt(rr->txt.items);
if (!t)
return -ENOMEM;
s = strjoin(k, " ", t, NULL);
if (!s)
return -ENOMEM;
-
break;
case DNS_TYPE_A: {
@@ -890,3 +926,32 @@ int dns_class_from_string(const char *s, uint16_t *class) {
return 0;
}
+
+DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i) {
+ DnsTxtItem *n;
+
+ if (!i)
+ return NULL;
+
+ n = i->items_next;
+
+ free(i);
+ return dns_txt_item_free_all(n);
+}
+
+bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) {
+
+ if (!a != !b)
+ return false;
+
+ if (!a)
+ return true;
+
+ if (a->length != b->length)
+ return false;
+
+ if (memcmp(a->data, b->data, a->length) != 0)
+ return false;
+
+ return dns_txt_item_equal(a->items_next, b->items_next);
+}
diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h
index a23546f757..c02f2ec00f 100644
--- a/src/resolve/resolved-dns-rr.h
+++ b/src/resolve/resolved-dns-rr.h
@@ -27,9 +27,11 @@
#include "dns-type.h"
#include "hashmap.h"
#include "in-addr-util.h"
+#include "list.h"
typedef struct DnsResourceKey DnsResourceKey;
typedef struct DnsResourceRecord DnsResourceRecord;
+typedef struct DnsTxtItem DnsTxtItem;
/* DNS record classes, see RFC 1035 */
enum {
@@ -45,6 +47,12 @@ struct DnsResourceKey {
char *_name; /* don't access directy, use DNS_RESOURCE_KEY_NAME()! */
};
+struct DnsTxtItem {
+ size_t length;
+ LIST_FIELDS(DnsTxtItem, items);
+ uint8_t data[];
+};
+
struct DnsResourceRecord {
unsigned n_ref;
DnsResourceKey *key;
@@ -73,7 +81,7 @@ struct DnsResourceRecord {
} hinfo;
struct {
- char **strings;
+ DnsTxtItem *items;
} txt, spf;
struct {
@@ -198,6 +206,9 @@ int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecor
int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceRecord*, dns_resource_record_unref);
+DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i);
+bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b);
+
const char *dns_class_to_string(uint16_t type);
int dns_class_from_string(const char *name, uint16_t *class);
diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c
index a588538b52..a4ca7c89d3 100644
--- a/src/resolve/resolved-manager.c
+++ b/src/resolve/resolved-manager.c
@@ -553,6 +553,9 @@ Manager *manager_free(Manager *m) {
sd_event_source_unref(m->network_event_source);
sd_network_monitor_unref(m->network_monitor);
+ sd_netlink_unref(m->rtnl);
+ sd_event_source_unref(m->rtnl_event_source);
+
manager_llmnr_stop(m);
sd_bus_slot_unref(m->prepare_for_sleep_slot);
diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c
index 423ccca9cc..e08143c3be 100644
--- a/src/shared/dns-domain.c
+++ b/src/shared/dns-domain.c
@@ -29,6 +29,7 @@
#include "hexdecoct.h"
#include "parse-util.h"
#include "string-util.h"
+#include "utf8.h"
int dns_label_unescape(const char **name, char *dest, size_t sz) {
const char *n;
@@ -749,3 +750,228 @@ int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len) {
return out - buffer;
}
+
+static bool srv_type_label_is_valid(const char *label, size_t n) {
+ size_t k;
+
+ assert(label);
+
+ if (n < 2) /* Label needs to be at least 2 chars long */
+ return false;
+
+ if (label[0] != '_') /* First label char needs to be underscore */
+ return false;
+
+ /* Second char must be a letter */
+ if (!(label[1] >= 'A' && label[1] <= 'Z') &&
+ !(label[1] >= 'a' && label[1] <= 'z'))
+ return false;
+
+ /* Third and further chars must be alphanumeric or a hyphen */
+ for (k = 2; k < n; k++) {
+ if (!(label[k] >= 'A' && label[k] <= 'Z') &&
+ !(label[k] >= 'a' && label[k] <= 'z') &&
+ !(label[k] >= '0' && label[k] <= '9') &&
+ label[k] != '-')
+ return false;
+ }
+
+ return true;
+}
+
+int dns_srv_type_verify(const char *name) {
+ unsigned c = 0;
+ int r;
+
+ if (!name)
+ return 0;
+
+ for (;;) {
+ char label[DNS_LABEL_MAX];
+
+ /* This more or less implements RFC 6335, Section 5.1 */
+
+ r = dns_label_unescape(&name, label, sizeof(label));
+ if (r == -EINVAL)
+ return 0;
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ if (c >= 2)
+ return 0;
+
+ if (!srv_type_label_is_valid(label, r))
+ return 0;
+
+ c++;
+ }
+
+ return c == 2; /* exactly two labels */
+}
+
+bool dns_service_name_is_valid(const char *name) {
+ size_t l;
+
+ /* This more or less implements RFC 6763, Section 4.1.1 */
+
+ if (!name)
+ return false;
+
+ if (!utf8_is_valid(name))
+ return false;
+
+ if (string_has_cc(name, NULL))
+ return false;
+
+ l = strlen(name);
+ if (l <= 0)
+ return false;
+ if (l > 63)
+ return false;
+
+ return true;
+}
+
+int dns_service_join(const char *name, const char *type, const char *domain, char **ret) {
+ _cleanup_free_ char *escaped = NULL, *n = NULL;
+ int r;
+
+ assert(type);
+ assert(domain);
+ assert(ret);
+
+ if (!dns_srv_type_verify(type))
+ return -EINVAL;
+
+ if (!name)
+ return dns_name_concat(type, domain, ret);
+
+ if (!dns_service_name_is_valid(name))
+ return -EINVAL;
+
+ r = dns_label_escape(name, strlen(name), &escaped);
+ if (r < 0)
+ return r;
+
+ r = dns_name_concat(type, domain, &n);
+ if (r < 0)
+ return r;
+
+ return dns_name_concat(escaped, n, ret);
+}
+
+static bool dns_service_name_label_is_valid(const char *label, size_t n) {
+ char *s;
+
+ assert(label);
+
+ if (memchr(label, 0, n))
+ return false;
+
+ s = strndupa(label, n);
+ return dns_service_name_is_valid(s);
+}
+
+int dns_service_split(const char *joined, char **_name, char **_type, char **_domain) {
+ _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL;
+ const char *p = joined, *q = NULL, *d = NULL;
+ char a[DNS_LABEL_MAX], b[DNS_LABEL_MAX], c[DNS_LABEL_MAX];
+ int an, bn, cn, r;
+ unsigned x = 0;
+
+ assert(joined);
+
+ /* Get first label from the full name */
+ an = dns_label_unescape(&p, a, sizeof(a));
+ if (an < 0)
+ return an;
+
+ if (an > 0) {
+ x++;
+
+ /* If there was a first label, try to get the second one */
+ bn = dns_label_unescape(&p, b, sizeof(b));
+ if (bn < 0)
+ return bn;
+
+ if (bn > 0) {
+ x++;
+
+ /* If there was a second label, try to get the third one */
+ q = p;
+ cn = dns_label_unescape(&p, c, sizeof(c));
+ if (cn < 0)
+ return cn;
+
+ if (cn > 0)
+ x++;
+ } else
+ cn = 0;
+ } else
+ an = 0;
+
+ if (x >= 2 && srv_type_label_is_valid(b, bn)) {
+
+ if (x >= 3 && srv_type_label_is_valid(c, cn)) {
+
+ if (dns_service_name_label_is_valid(a, an)) {
+
+ /* OK, got <name> . <type> . <type2> . <domain> */
+
+ name = strndup(a, an);
+ if (!name)
+ return -ENOMEM;
+
+ type = new(char, bn+1+cn+1);
+ if (!type)
+ return -ENOMEM;
+ strcpy(stpcpy(stpcpy(type, b), "."), c);
+
+ d = p;
+ goto finish;
+ }
+
+ } else if (srv_type_label_is_valid(a, an)) {
+
+ /* OK, got <type> . <type2> . <domain> */
+
+ name = NULL;
+
+ type = new(char, an+1+bn+1);
+ if (!type)
+ return -ENOMEM;
+ strcpy(stpcpy(stpcpy(type, a), "."), b);
+
+ d = q;
+ goto finish;
+ }
+ }
+
+ name = NULL;
+ type = NULL;
+ d = joined;
+
+finish:
+ r = dns_name_normalize(d, &domain);
+ if (r < 0)
+ return r;
+
+ if (_domain) {
+ *_domain = domain;
+ domain = NULL;
+ }
+
+ if (_type) {
+ *_type = type;
+ type = NULL;
+ }
+
+ if (_name) {
+ *_name = name;
+ name = NULL;
+ }
+
+ return 0;
+}
diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h
index b214897440..c6d7623e09 100644
--- a/src/shared/dns-domain.h
+++ b/src/shared/dns-domain.h
@@ -69,3 +69,10 @@ int dns_name_root(const char *name);
int dns_name_single_label(const char *name);
int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len);
+
+int dns_srv_type_verify(const char *name);
+
+bool dns_service_name_is_valid(const char *name);
+
+int dns_service_join(const char *name, const char *type, const char *domain, char **ret);
+int dns_service_split(const char *joined, char **name, char **type, char **domain);
diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c
index 407c7d8ef7..a664e1c79c 100644
--- a/src/test/test-dns-domain.c
+++ b/src/test/test-dns-domain.c
@@ -316,6 +316,98 @@ static void test_dns_name_is_valid(void) {
test_dns_name_is_valid_one("\n", 0);
}
+static void test_dns_service_name_is_valid(void) {
+ assert_se(dns_service_name_is_valid("Lennart's Compüter"));
+ assert_se(dns_service_name_is_valid("piff.paff"));
+
+ assert_se(!dns_service_name_is_valid(NULL));
+ assert_se(!dns_service_name_is_valid(""));
+ assert_se(!dns_service_name_is_valid("foo\nbar"));
+ assert_se(!dns_service_name_is_valid("foo\201bar"));
+ assert_se(!dns_service_name_is_valid("this is an overly long string that is certainly longer than 63 characters"));
+}
+
+static void test_dns_srv_type_verify(void) {
+
+ assert_se(dns_srv_type_verify("_http._tcp") > 0);
+ assert_se(dns_srv_type_verify("_foo-bar._tcp") > 0);
+ assert_se(dns_srv_type_verify("_w._udp") > 0);
+ assert_se(dns_srv_type_verify("_a800._tcp") > 0);
+ assert_se(dns_srv_type_verify("_a-800._tcp") > 0);
+
+ assert_se(dns_srv_type_verify(NULL) == 0);
+ assert_se(dns_srv_type_verify("") == 0);
+ assert_se(dns_srv_type_verify("x") == 0);
+ assert_se(dns_srv_type_verify("_foo") == 0);
+ assert_se(dns_srv_type_verify("_tcp") == 0);
+ assert_se(dns_srv_type_verify("_") == 0);
+ assert_se(dns_srv_type_verify("_foo.") == 0);
+ assert_se(dns_srv_type_verify("_föo._tcp") == 0);
+ assert_se(dns_srv_type_verify("_f\no._tcp") == 0);
+ assert_se(dns_srv_type_verify("_800._tcp") == 0);
+ assert_se(dns_srv_type_verify("_-800._tcp") == 0);
+ assert_se(dns_srv_type_verify("_-foo._tcp") == 0);
+ assert_se(dns_srv_type_verify("_piep._foo._udp") == 0);
+}
+
+static void test_dns_service_join_one(const char *a, const char *b, const char *c, int r, const char *d) {
+ _cleanup_free_ char *x = NULL, *y = NULL, *z = NULL, *t = NULL;
+
+ assert_se(dns_service_join(a, b, c, &t) == r);
+ assert_se(streq_ptr(t, d));
+
+ if (r < 0)
+ return;
+
+ assert_se(dns_service_split(t, &x, &y, &z) >= 0);
+ assert_se(streq_ptr(a, x));
+ assert_se(streq_ptr(b, y));
+ assert_se(streq_ptr(c, z));
+}
+
+static void test_dns_service_join(void) {
+ test_dns_service_join_one("", "", "", -EINVAL, NULL);
+ test_dns_service_join_one("", "_http._tcp", "", -EINVAL, NULL);
+ test_dns_service_join_one("", "_http._tcp", "foo", -EINVAL, NULL);
+ test_dns_service_join_one("foo", "", "foo", -EINVAL, NULL);
+ test_dns_service_join_one("foo", "foo", "foo", -EINVAL, NULL);
+
+ test_dns_service_join_one("foo", "_http._tcp", "", 0, "foo._http._tcp");
+ test_dns_service_join_one(NULL, "_http._tcp", "", 0, "_http._tcp");
+ test_dns_service_join_one("foo", "_http._tcp", "foo", 0, "foo._http._tcp.foo");
+ test_dns_service_join_one(NULL, "_http._tcp", "foo", 0, "_http._tcp.foo");
+ test_dns_service_join_one("Lennart's PC", "_pc._tcp", "foo.bar.com", 0, "Lennart\\039s\\032PC._pc._tcp.foo.bar.com");
+ test_dns_service_join_one(NULL, "_pc._tcp", "foo.bar.com", 0, "_pc._tcp.foo.bar.com");
+}
+
+static void test_dns_service_split_one(const char *joined, const char *a, const char *b, const char *c, int r) {
+ _cleanup_free_ char *x = NULL, *y = NULL, *z = NULL, *t = NULL;
+
+ assert_se(dns_service_split(joined, &x, &y, &z) == r);
+ assert_se(streq_ptr(x, a));
+ assert_se(streq_ptr(y, b));
+ assert_se(streq_ptr(z, c));
+
+ if (r < 0)
+ return;
+
+ if (y) {
+ assert_se(dns_service_join(x, y, z, &t) == 0);
+ assert_se(streq_ptr(joined, t));
+ } else
+ assert_se(!x && streq_ptr(z, joined));
+}
+
+static void test_dns_service_split(void) {
+ test_dns_service_split_one("", NULL, NULL, "", 0);
+ test_dns_service_split_one("foo", NULL, NULL, "foo", 0);
+ test_dns_service_split_one("foo.bar", NULL, NULL, "foo.bar", 0);
+ test_dns_service_split_one("_foo.bar", NULL, NULL, "_foo.bar", 0);
+ test_dns_service_split_one("_foo._bar", NULL, "_foo._bar", "", 0);
+ test_dns_service_split_one("_meh._foo._bar", "_meh", "_foo._bar", "", 0);
+ test_dns_service_split_one("Wuff\\032Wuff._foo._bar.waldo.com", "Wuff Wuff", "_foo._bar", "waldo.com", 0);
+}
+
int main(int argc, char *argv[]) {
test_dns_label_unescape();
@@ -331,6 +423,10 @@ int main(int argc, char *argv[]) {
test_dns_name_concat();
test_dns_name_is_valid();
test_dns_name_to_wire_format();
+ test_dns_service_name_is_valid();
+ test_dns_srv_type_verify();
+ test_dns_service_join();
+ test_dns_service_split();
return 0;
}