From 2d4c5cbc0ed3ccb09dc086a040088b454c22c644 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 30 Jul 2014 19:23:27 +0200 Subject: [PATCH 1/1] resolved: add API for resolving specific RRs --- Makefile.am | 12 +- src/resolve-host/resolve-host.c | 149 ++++++++++++++++++-- src/resolve/resolved-bus.c | 163 +++++++++++++++++++--- src/resolve/resolved-dns-rr.c | 233 ++++++++++++++++++++++++-------- src/resolve/resolved-dns-rr.h | 5 + 5 files changed, 481 insertions(+), 81 deletions(-) diff --git a/Makefile.am b/Makefile.am index 9e243eb1e..bc16ce315 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4848,7 +4848,17 @@ lib_LTLIBRARIES += \ libnss_resolve.la systemd_resolve_host_SOURCES = \ - src/resolve-host/resolve-host.c + src/resolve-host/resolve-host.c \ + src/resolve/resolved-dns-packet.c \ + src/resolve/resolved-dns-packet.h \ + src/resolve/resolved-dns-rr.c \ + src/resolve/resolved-dns-rr.h \ + src/resolve/resolved-dns-answer.c \ + src/resolve/resolved-dns-answer.h \ + src/resolve/resolved-dns-question.c \ + src/resolve/resolved-dns-question.h \ + src/resolve/resolved-dns-domain.c \ + src/resolve/resolved-dns-domain.h systemd_resolve_host_LDADD = \ libsystemd-internal.la \ diff --git a/src/resolve-host/resolve-host.c b/src/resolve-host/resolve-host.c index 122c2622c..1b1edaf6e 100644 --- a/src/resolve-host/resolve-host.c +++ b/src/resolve-host/resolve-host.c @@ -31,10 +31,14 @@ #include "af-list.h" #include "build.h" +#include "resolved-dns-packet.h" + #define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) static int arg_family = AF_UNSPEC; static int arg_ifindex = 0; +static uint16_t arg_type = 0; +static uint16_t arg_class = 0; static int resolve_host(sd_bus *bus, const char *name) { @@ -286,6 +290,103 @@ static int parse_address(const char *s, int *family, union in_addr_union *addres return 0; } +static int resolve_record(sd_bus *bus, const char *name) { + + _cleanup_bus_message_unref_ sd_bus_message *req = NULL, *reply = NULL; + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + unsigned n = 0; + int r; + + assert(name); + + log_debug("Resolving %s %s %s.", name, dns_class_to_string(arg_class), dns_type_to_string(arg_type)); + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveRecord"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_auto_start(req, false); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "sqq", name, arg_class, arg_type); + if (r < 0) + return bus_log_create_error(r); + + 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; + } + + r = sd_bus_message_enter_container(reply, 'a', "(qqay)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_enter_container(reply, 'r', "qqay")) > 0) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + _cleanup_free_ char *s = NULL; + uint16_t c, t; + const void *d; + size_t l; + + r = sd_bus_message_read(reply, "qq", &c, &t); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', &d, &l); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0); + if (r < 0) + return log_oom(); + + r = dns_packet_append_blob(p, d, l, NULL); + if (r < 0) + return log_oom(); + + r = dns_packet_read_rr(p, &rr, NULL); + if (r < 0) { + log_error("Failed to parse RR."); + return r; + } + + r = dns_resource_record_to_string(rr, &s); + if (r < 0) { + log_error("Failed to format RR."); + return r; + } + + printf("%s\n", s); + n++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (n == 0) { + log_error("%s: no records found", name); + return -ESRCH; + } + + return 0; +} + static void help(void) { printf("%s [OPTIONS...]\n\n" "Resolve IPv4 or IPv6 addresses.\n\n" @@ -294,6 +395,8 @@ static void help(void) { " -4 Resolve IPv4 addresses\n" " -6 Resolve IPv6 addresses\n" " -i INTERFACE Filter by interface\n" + " -t --type=TYPE Query RR with DNS type\n" + " -c --class=CLASS Query RR with DNS class\n" , program_invocation_short_name); } @@ -305,15 +408,17 @@ static int parse_argv(int argc, char *argv[]) { static const struct option options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, ARG_VERSION }, + { "type", no_argument, NULL, 't' }, + { "class", no_argument, NULL, 'c' }, {} }; - int c; + int c, r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h46i:", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "h46i:t:c:", options, NULL)) >= 0) { switch(c) { case 'h': @@ -337,7 +442,23 @@ static int parse_argv(int argc, char *argv[]) { arg_ifindex = if_nametoindex(optarg); if (arg_ifindex <= 0) { log_error("Unknown interfaces %s: %m", optarg); - return -EINVAL; + return -errno; + } + break; + + case 't': + r = dns_type_from_string(optarg, &arg_type); + if (r < 0) { + log_error("Failed to parse RR record type %s", optarg); + return r; + } + break; + + case 'c': + r = dns_class_from_string(optarg, &arg_class); + if (r < 0) { + log_error("Failed to parse RR record class %s", optarg); + return r; } break; @@ -349,6 +470,14 @@ 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="); + return -EINVAL; + } + + if (arg_type != 0 && arg_class == 0) + arg_class = DNS_CLASS_IN; + return 1 /* work to do */; } @@ -379,11 +508,15 @@ int main(int argc, char **argv) { int family, ifindex, k; union in_addr_union a; - k = parse_address(argv[optind], &family, &a, &ifindex); - if (k >= 0) - k = resolve_address(bus, family, &a, ifindex); - else - k = resolve_host(bus, argv[optind]); + if (arg_type != 0) + k = resolve_record(bus, argv[optind]); + else { + k = parse_address(argv[optind], &family, &a, &ifindex); + if (k >= 0) + k = resolve_address(bus, family, &a, ifindex); + else + k = resolve_host(bus, argv[optind]); + } if (r == 0) r = k; diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index d0c21894e..0b8ecf1b8 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -157,13 +157,13 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { for (i = 0; i < answer->n_rrs; i++) { r = dns_question_matches_rr(q->question, answer->rrs[i]); if (r < 0) - goto parse_fail; + 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 parse_fail; + goto finish; if (r > 0) cname = dns_resource_record_ref(answer->rrs[i]); @@ -204,7 +204,7 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { for (i = 0; i < answer->n_rrs; i++) { r = dns_question_matches_rr(q->question, answer->rrs[i]); if (r < 0) - goto parse_fail; + goto finish; if (r == 0) continue; @@ -230,6 +230,7 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { r = sd_bus_reply_method_errno(q->request, -r, NULL); goto finish; } + return; } } @@ -245,14 +246,12 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { goto finish; r = sd_bus_send(q->manager->bus, reply, NULL); - goto finish; - -parse_fail: - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_INVALID_REPLY, "Received invalid reply"); finish: - if (r < 0) - log_error("Failed to send bus reply: %s", strerror(-r)); + 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); } @@ -356,7 +355,7 @@ static void bus_method_resolve_address_complete(DnsQuery *q) { for (i = 0; i < answer->n_rrs; i++) { r = dns_question_matches_rr(q->question, answer->rrs[i]); if (r < 0) - goto parse_fail; + goto finish; if (r == 0) continue; @@ -382,14 +381,12 @@ static void bus_method_resolve_address_complete(DnsQuery *q) { goto finish; r = sd_bus_send(q->manager->bus, reply, NULL); - goto finish; - -parse_fail: - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_INVALID_REPLY, "Received invalid reply"); finish: - if (r < 0) - log_error("Failed to send bus reply: %s", strerror(-r)); + 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); } @@ -469,10 +466,144 @@ static int bus_method_resolve_address(sd_bus *bus, sd_bus_message *message, void 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_QUERY_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_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_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; + _cleanup_free_ char *reverse = NULL; + Manager *m = userdata; + DnsQuery *q; + int r; + uint16_t class, type; + const char *name; + + assert(bus); + assert(message); + assert(m); + + r = sd_bus_message_read(message, "sqq", &name, &class, &type); + 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); + 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_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", "si", "a(iayi)s", bus_method_resolve_hostname, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("ResolveAddress", "iayi", "as", bus_method_resolve_address, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ResolveRecord", "sqq", "a(qqay)", bus_method_resolve_record, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END, }; diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index 8b8858848..e1200c9bc 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -159,6 +159,31 @@ int dns_resource_key_compare_func(const void *a, const void *b) { return 0; } +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; + } + + s = strjoin(DNS_RESOURCE_KEY_NAME(key), " ", c, " ", t, NULL); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key) { DnsResourceRecord *rr; @@ -267,16 +292,24 @@ int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecor if (r <= 0) return r; - if (IN_SET(a->key->type, DNS_TYPE_PTR, DNS_TYPE_NS, DNS_TYPE_CNAME)) + switch (a->key->type) { + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: return dns_name_equal(a->ptr.name, b->ptr.name); - else if (a->key->type == DNS_TYPE_HINFO) - return strcasecmp(a->hinfo.cpu, b->hinfo.cpu) == 0 && - strcasecmp(a->hinfo.os, b->hinfo.os) == 0; - else if (a->key->type == DNS_TYPE_A) + + case DNS_TYPE_HINFO: + return strcaseeq(a->hinfo.cpu, b->hinfo.cpu) && + strcaseeq(a->hinfo.os, b->hinfo.os); + + case DNS_TYPE_A: return memcmp(&a->a.in_addr, &b->a.in_addr, sizeof(struct in_addr)) == 0; - else if (a->key->type == DNS_TYPE_AAAA) + + case DNS_TYPE_AAAA: return memcmp(&a->aaaa.in6_addr, &b->aaaa.in6_addr, sizeof(struct in6_addr)) == 0; - else if (a->key->type == DNS_TYPE_SOA) { + + case DNS_TYPE_SOA: r = dns_name_equal(a->soa.mname, b->soa.mname); if (r <= 0) return r; @@ -289,83 +322,171 @@ int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecor a->soa.retry == b->soa.retry && a->soa.expire == b->soa.expire && a->soa.minimum == b->soa.minimum; - } else + default: return a->generic.size == b->generic.size && memcmp(a->generic.data, b->generic.data, a->generic.size) == 0; + } } -const char *dns_class_to_string(uint16_t class) { +int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { + _cleanup_free_ char *k = NULL; + char *s; + int r; - switch (class) { + assert(rr); - case DNS_CLASS_IN: - return "IN"; + r = dns_resource_key_to_string(rr->key, &k); + if (r < 0) + return r; - case DNS_CLASS_ANY: - return "ANY"; - } + switch (rr->key->type) { - return NULL; -} + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + s = strjoin(k, " ", rr->ptr.name, NULL); + if (!s) + return -ENOMEM; -const char *dns_type_to_string(uint16_t type) { + break; - switch (type) { + case DNS_TYPE_HINFO: + s = strjoin(k, " ", rr->hinfo.cpu, " ", rr->hinfo.os, NULL); + if (!s) + return -ENOMEM; + break; - case DNS_TYPE_A: - return "A"; + case DNS_TYPE_A: { + _cleanup_free_ char *x = NULL; - case DNS_TYPE_NS: - return "NS"; + r = in_addr_to_string(AF_INET, (const union in_addr_union*) &rr->a.in_addr, &x); + if (r < 0) + return r; - case DNS_TYPE_CNAME: - return "CNAME"; + s = strjoin(k, " ", x, NULL); + if (!s) + return -ENOMEM; + break; + } - case DNS_TYPE_SOA: - return "SOA"; + case DNS_TYPE_AAAA: { + _cleanup_free_ char *x = NULL; - case DNS_TYPE_PTR: - return "PTR"; + r = in_addr_to_string(AF_INET6, (const union in_addr_union*) &rr->aaaa.in6_addr, &x); + if (r < 0) + return r; - case DNS_TYPE_HINFO: - return "HINFO"; + s = strjoin(k, " ", x, NULL); + if (!s) + return -ENOMEM; + break; + } - case DNS_TYPE_MX: - return "MX"; + 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; + + default: { + _cleanup_free_ char *x = NULL; + + x = hexmem(rr->generic.data, rr->generic.size); + if (!x) + return -ENOMEM; + + s = strjoin(k, " ", x, NULL); + if (!s) + return -ENOMEM; + break; + }} + + *ret = s; + return 0; +} - case DNS_TYPE_TXT: - return "TXT"; +const char *dns_class_to_string(uint16_t class) { - case DNS_TYPE_AAAA: - return "AAAA"; + switch (class) { - case DNS_TYPE_SRV: - return "SRV"; + case DNS_CLASS_IN: + return "IN"; - case DNS_TYPE_SSHFP: - return "SSHFP"; + case DNS_CLASS_ANY: + return "ANY"; + } - case DNS_TYPE_DNAME: - return "DNAME"; + return NULL; +} - case DNS_TYPE_ANY: - return "ANY"; +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; - case DNS_TYPE_OPT: - return "OPT"; + return 0; +} - case DNS_TYPE_TKEY: - return "TKEY"; +static const struct { + uint16_t type; + const char *name; +} dns_types[] = { + { DNS_TYPE_A, "A" }, + { DNS_TYPE_NS, "NS" }, + { DNS_TYPE_CNAME, "CNAME" }, + { DNS_TYPE_SOA, "SOA" }, + { DNS_TYPE_PTR, "PTR" }, + { DNS_TYPE_HINFO, "HINFO" }, + { DNS_TYPE_MX, "MX" }, + { DNS_TYPE_TXT, "TXT" }, + { DNS_TYPE_AAAA, "AAAA" }, + { DNS_TYPE_SRV, "SRV" }, + { DNS_TYPE_SSHFP, "SSHFP" }, + { DNS_TYPE_DNAME, "DNAME" }, + { DNS_TYPE_ANY, "ANY" }, + { DNS_TYPE_OPT, "OPT" }, + { DNS_TYPE_TKEY, "TKEY" }, + { DNS_TYPE_TSIG, "TSIG" }, + { DNS_TYPE_IXFR, "IXFR" }, + { DNS_TYPE_AXFR, "AXFR" }, +}; - case DNS_TYPE_TSIG: - return "TSIG"; - case DNS_TYPE_IXFR: - return "IXFR"; +const char *dns_type_to_string(uint16_t type) { + unsigned i; - case DNS_TYPE_AXFR: - return "AXFR"; - } + for (i = 0; i < ELEMENTSOF(dns_types); i++) + if (dns_types[i].type == type) + return dns_types[i].name; return NULL; } + +int dns_type_from_string(const char *s, uint16_t *type) { + unsigned i; + + assert(s); + assert(type); + + for (i = 0; i < ELEMENTSOF(dns_types); i++) + if (strcaseeq(dns_types[i].name, s)) { + *type = dns_types[i].type; + return 0; + } + + return -EINVAL; +} diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index 50bb74c67..524630071 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -137,6 +137,7 @@ int dns_resource_key_match_rr(const DnsResourceKey *key, const DnsResourceRecord int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRecord *rr); unsigned long dns_resource_key_hash_func(const void *i, const uint8_t hash_key[HASH_KEY_SIZE]); int dns_resource_key_compare_func(const void *a, const void *b); +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); @@ -145,7 +146,11 @@ 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_type_to_string(uint16_t type); +int dns_type_from_string(const char *name, uint16_t *type); + const char *dns_class_to_string(uint16_t type); +int dns_class_from_string(const char *name, uint16_t *class); -- 2.30.2