diff --git a/Makefile b/Makefile
index 599b1452a05a..80d82e538a48 100644
--- a/Makefile
+++ b/Makefile
@@ -159,12 +159,14 @@ keyctl_watch.o: watch_queue.h
 request-key: request-key.o $(LIB_DEPENDENCY)
 	$(CC) -L. $(CFLAGS) $(LDFLAGS) $(RPATH) -o $@ $< -lkeyutils
 
-key.dns_resolver: key.dns_resolver.o dns.afsdb.o $(LIB_DEPENDENCY)
+key.dns_resolver: key.dns_resolver.o dns.afsdb.o key.dns.h key.dns.o $(LIB_DEPENDENCY)
 	$(CC) -L. $(CFLAGS) $(LDFLAGS) $(RPATH) -o $@ \
-		key.dns_resolver.o dns.afsdb.o -lkeyutils -lresolv
+		key.dns_resolver.o dns.afsdb.o key.dns.o \
+		-lkeyutils -lresolv
 
 key.dns_resolver.o: key.dns_resolver.c key.dns.h
 dns.afsdb.o: dns.afsdb.c key.dns.h
+key.dns.o: key.dns.c key.dns.h
 
 ###############################################################################
 #
diff --git a/dns.afsdb.c b/dns.afsdb.c
index 986c0f385a1a..2241cf58e55c 100644
--- a/dns.afsdb.c
+++ b/dns.afsdb.c
@@ -38,296 +38,100 @@
 #include "key.dns.h"
 
 /*
- *
- */
-static void afsdb_hosts_to_addrs(ns_msg handle, ns_sect section)
-{
-	char *vllist[MAX_VLS];	/* list of name servers	*/
-	int vlsnum = 0;		/* number of name servers in list */
-	int rrnum;
-	ns_rr rr;
-	int subtype, i, ret;
-	unsigned int ttl = UINT_MAX, rr_ttl;
-
-	debug("AFSDB RR count is %d", ns_msg_count(handle, section));
-
-	/* Look at all the resource records in this section. */
-	for (rrnum = 0; rrnum < ns_msg_count(handle, section); rrnum++) {
-		/* Expand the resource record number rrnum into rr. */
-		if (ns_parserr(&handle, section, rrnum, &rr)) {
-			_error("ns_parserr failed : %m");
-			continue;
-		}
-
-		/* We're only interested in AFSDB records */
-		if (ns_rr_type(rr) == ns_t_afsdb) {
-			vllist[vlsnum] = malloc(MAXDNAME);
-			if (!vllist[vlsnum])
-				error("Out of memory");
-
-			subtype = ns_get16(ns_rr_rdata(rr));
-
-			/* Expand the name server's domain name */
-			if (ns_name_uncompress(ns_msg_base(handle),
-					       ns_msg_end(handle),
-					       ns_rr_rdata(rr) + 2,
-					       vllist[vlsnum],
-					       MAXDNAME) < 0)
-				error("ns_name_uncompress failed");
-
-			rr_ttl = ns_rr_ttl(rr);
-			if (ttl > rr_ttl)
-				ttl = rr_ttl;
-
-			/* Check the domain name we've just unpacked and add it to
-			 * the list of VL servers if it is not a duplicate.
-			 * If it is a duplicate, just ignore it.
-			 */
-			for (i = 0; i < vlsnum; i++)
-				if (strcasecmp(vllist[i], vllist[vlsnum]) == 0)
-					goto next_one;
-
-			/* Turn the hostname into IP addresses */
-			ret = dns_resolver(vllist[vlsnum], NULL);
-			if (ret) {
-				debug("AFSDB RR can't resolve."
-				      "subtype:%d, server name:%s, netmask:%u",
-				      subtype, vllist[vlsnum], mask);
-				goto next_one;
-			}
-
-			info("AFSDB RR subtype:%d, server name:%s, ip:%*.*s, ttl:%u",
-			     subtype, vllist[vlsnum],
-			     (int)payload[payload_index - 1].iov_len,
-			     (int)payload[payload_index - 1].iov_len,
-			     (char *)payload[payload_index - 1].iov_base,
-			     ttl);
-
-			/* prepare for the next record */
-			vlsnum++;
-			continue;
-
-		next_one:
-			free(vllist[vlsnum]);
-		}
-	}
-
-	key_expiry = ttl;
-	info("ttl: %u", key_expiry);
-}
-
-/*
- *
- */
-static void srv_hosts_to_addrs(ns_msg handle, ns_sect section)
-{
-	char *vllist[MAX_VLS];	/* list of name servers	*/
-	int vlsnum = 0;		/* number of name servers in list */
-	int rrnum;
-	ns_rr rr;
-	int subtype, i, ret;
-	unsigned short pref, weight, port;
-	unsigned int ttl = UINT_MAX, rr_ttl;
-	char sport[8];
-
-	debug("SRV RR count is %d", ns_msg_count(handle, section));
-
-	/* Look at all the resource records in this section. */
-	for (rrnum = 0; rrnum < ns_msg_count(handle, section); rrnum++) {
-		/* Expand the resource record number rrnum into rr. */
-		if (ns_parserr(&handle, section, rrnum, &rr)) {
-			_error("ns_parserr failed : %m");
-			continue;
-		}
-
-		if (ns_rr_type(rr) == ns_t_srv) {
-			vllist[vlsnum] = malloc(MAXDNAME);
-			if (!vllist[vlsnum])
-				error("Out of memory");
-
-			subtype = ns_get16(ns_rr_rdata(rr));
-
-			/* Expand the name server's domain name */
-			if (ns_name_uncompress(ns_msg_base(handle),
-					       ns_msg_end(handle),
-					       ns_rr_rdata(rr) + 6,
-					       vllist[vlsnum],
-					       MAXDNAME) < 0) {
-				_error("ns_name_uncompress failed");
-				continue;
-			}
-
-			rr_ttl = ns_rr_ttl(rr);
-			if (ttl > rr_ttl)
-				ttl = rr_ttl;
-
-			pref   = ns_get16(ns_rr_rdata(rr));
-			weight = ns_get16(ns_rr_rdata(rr) + 2);
-			port   = ns_get16(ns_rr_rdata(rr) + 4);
-			info("rdata %u %u %u", pref, weight, port);
-
-			sprintf(sport, "+%hu", port);
-
-			/* Check the domain name we've just unpacked and add it to
-			 * the list of VL servers if it is not a duplicate.
-			 * If it is a duplicate, just ignore it.
-			 */
-			for (i = 0; i < vlsnum; i++)
-				if (strcasecmp(vllist[i], vllist[vlsnum]) == 0)
-					goto next_one;
-
-			/* Turn the hostname into IP addresses */
-			ret = dns_resolver(vllist[vlsnum], sport);
-			if (ret) {
-				debug("SRV RR can't resolve."
-				      "subtype:%d, server name:%s, netmask:%u",
-				      subtype, vllist[vlsnum], mask);
-				goto next_one;
-			}
-
-			info("SRV RR subtype:%d, server name:%s, ip:%*.*s, ttl:%u",
-			     subtype, vllist[vlsnum],
-			     (int)payload[payload_index - 1].iov_len,
-			     (int)payload[payload_index - 1].iov_len,
-			     (char *)payload[payload_index - 1].iov_base,
-			     ttl);
-
-			/* prepare for the next record */
-			vlsnum++;
-			continue;
-
-		next_one:
-			free(vllist[vlsnum]);
-		}
-	}
-
-	key_expiry = ttl;
-	info("ttl: %u", key_expiry);
-}
-
-/*
- * Look up an AFSDB record to get the VL server addresses.
+ * Instantiate the key.
  */
-static int dns_query_AFSDB(const char *cell)
+static int afs_instantiate(payload_t *payload, unsigned int ttl)
 {
-	int	response_len;		/* buffer length */
-	ns_msg	handle;			/* handle for response message */
-	union {
-		HEADER hdr;
-		u_char buf[NS_PACKETSZ];
-	} response;		/* response buffers */
-
-	debug("Get AFSDB RR for cell name:'%s'", cell);
-
-	/* query the dns for an AFSDB resource record */
-	response_len = res_query(cell,
-				 ns_c_in,
-				 ns_t_afsdb,
-				 response.buf,
-				 sizeof(response));
+	int ret = 0;
 
-	if (response_len < 0) {
-		/* negative result */
-		_nsError(h_errno, cell);
-		return -1;
+	/* set the key's expiry time from the minimum TTL encountered */
+	ret = keyctl_set_timeout(key, ttl);
+	if (ret) {
+		error("%s: keyctl_set_timeout: %m", __func__);
+		return ret;
 	}
 
-	if (ns_initparse(response.buf, response_len, &handle) < 0)
-		error("ns_initparse: %m");
-
-	/* look up the hostnames we've obtained to get the actual addresses */
-	afsdb_hosts_to_addrs(handle, ns_s_an);
+	/* instantiate the key */
+	ret = keyctl_instantiate_iov(key, payload->data, payload->index, 0);
+	if (ret)
+		error("%s: keyctl_instantiate: %m", __func__);
 
-	info("DNS query AFSDB RR results:%u ttl:%u", payload_index, key_expiry);
-	return 0;
+	return ret;
 }
 
 /*
- * Look up an SRV record to get the VL server addresses [RFC 5864].
+ * Lookup VL servers for AFS.
  */
-static int dns_query_VL_SRV(const char *cell)
+__attribute__((noreturn))
+void afs_lookup_VL_servers(const char *cell, char *options)
 {
-	int	response_len;		/* buffer length */
-	ns_msg	handle;			/* handle for response message */
-	union {
-		HEADER hdr;
-		u_char buf[NS_PACKETSZ];
-	} response;
-	char name[1024];
-
-	snprintf(name, sizeof(name), "_afs3-vlserver._udp.%s", cell);
-
-	debug("Get VL SRV RR for name:'%s'", name);
-
-	response_len = res_query(name,
-				 ns_c_in,
-				 ns_t_srv,
-				 response.buf,
-				 sizeof(response));
-
-	if (response_len < 0) {
-		/* negative result */
-		_nsError(h_errno, cell);
-		return -1;
+	payload_t *payload = NULL;
+	struct hostinfo host = { 0 };
+	char *vlsrv_name = NULL;
+	int ret = 0;
+
+	if (!cell)
+		error_ex("%s: missing hostname", __func__);
+
+	CALLOC_CHECK(vlsrv_name, 1, MAXDNAME);
+	snprintf(vlsrv_name, MAXDNAME, "_afs3-vlserver._udp.%s", cell);
+	STRNDUP_CHECK(host.hostname, vlsrv_name, strlen(vlsrv_name));
+	CALLOC_CHECK(payload, 1, sizeof(payload_t));
+
+	free(vlsrv_name);
+
+	host.af = AF_UNSPEC;
+	host.single_addr = true;
+
+	parse_opts(&host, options);
+
+	/*
+	 * Look up an SRV record to get the VL server addresses [RFC 5864].
+	 */
+	host.type = ns_t_srv;
+	ret = dns_resolver(&host, payload);
+	if (ret) {
+		free(host.hostname);
+		STRNDUP_CHECK(host.hostname, cell, strlen(cell));
+
+		/*
+		 * Look up an AFSDB record to get the VL server addresses.
+		 */
+		host.type = ns_t_afsdb;
+		ret = dns_resolver(&host, payload);
 	}
 
-	if (ns_initparse(response.buf, response_len, &handle) < 0)
-		error("ns_initparse: %m");
-
-	/* look up the hostnames we've obtained to get the actual addresses */
-	srv_hosts_to_addrs(handle, ns_s_an);
-
-	info("DNS query VL SRV RR results:%u ttl:%u", payload_index, key_expiry);
-	return 0;
-}
-
-/*
- * Instantiate the key.
- */
-static __attribute__((noreturn))
-void afs_instantiate(const char *cell)
-{
-	int ret;
-
-	/* set the key's expiry time from the minimum TTL encountered */
-	if (!debug_mode) {
-		ret = keyctl_set_timeout(key, key_expiry);
-		if (ret == -1)
-			error("%s: keyctl_set_timeout: %m", __func__);
+	if (ret || payload->index == 0) {
+		ret = get_err(0);
+		goto out_free;
 	}
 
-	/* handle a lack of results */
-	if (payload_index == 0)
-		nsError(NO_DATA, cell);
-
-	/* must include a NUL char at the end of the payload */
-	payload[payload_index].iov_base = "";
-	payload[payload_index++].iov_len = 1;
-	dump_payload();
+	dump_payload(payload);
+	info("Key timeout will be %ld seconds", host.ttl);
 
-	/* load the key with data key */
 	if (!debug_mode) {
-		ret = keyctl_instantiate_iov(key, payload, payload_index, 0);
-		if (ret == -1)
-			error("%s: keyctl_instantiate: %m", __func__);
+		unsigned int ttl;
+
+		/*
+		 * If TTL was set through the config file (key_expiry),
+		 * it takes precedence over the one from the DNS record (stored
+		 * in host.ttl).
+		 */
+		if (key_expiry > -1)
+			ttl = (unsigned int)key_expiry;
+		else if (host.ttl > -1)
+			ttl = (unsigned int)host.ttl;
+		else
+			/* Fallback to default value if dns_resolver() couldn't
+			 * get TTL for some reason */
+			ttl = DEFAULT_KEY_TTL;
+
+		ret = afs_instantiate(payload, ttl);
 	}
 
-	exit(0);
-}
-
-/*
- * Look up VL servers for AFS.
- */
-void afs_look_up_VL_servers(const char *cell, char *options)
-{
-	/* Is the IP address family limited? */
-	if (strcmp(options, "ipv4") == 0)
-		mask = INET_IP4_ONLY;
-	else if (strcmp(options, "ipv6") == 0)
-		mask = INET_IP6_ONLY;
-
-	if (dns_query_VL_SRV(cell) != 0)
-		dns_query_AFSDB(cell);
+out_free:
+	free_hostinfo(&host);
+	free(payload);
 
-	afs_instantiate(cell);
+	exit(ret);
 }
diff --git a/key.dns.c b/key.dns.c
new file mode 100644
index 000000000000..6390f3118a6e
--- /dev/null
+++ b/key.dns.c
@@ -0,0 +1,794 @@
+/*
+ * Common DNS resolving code for keyutils
+ *
+ * Copyright (c) 2022, SUSE LLC
+ * Author: Enzo Matsumiya <ematsumiya@suse.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "key.dns.h"
+
+#define clamp(x, lo, hi) (MIN(hi, MAX(x, lo)))
+
+static const int ns_errno_map[] = {
+	[0]			= ECONNREFUSED,
+	[HOST_NOT_FOUND]	= ENODATA,
+	[TRY_AGAIN]		= EAGAIN,
+	[NO_RECOVERY]		= ECONNREFUSED,
+	[NO_DATA]		= ENODATA,
+};
+static const int ns_errno_max = sizeof(ns_errno_map) / sizeof(ns_errno_map[0]);
+
+/*
+ * Returns -errno, @optval, -h_errno, or -1, in that order.
+ *
+ * Resets errno and h_errno to 0 unconditionally.
+ */
+int get_err(int optval)
+{
+	int ret = -1;
+
+	if (errno)
+		ret = -errno;
+	else if (optval)
+		ret = optval;
+	else if (h_errno >= ns_errno_max)
+		ret = -ECONNREFUSED;
+	else if (h_errno)
+		ret = -(ns_errno_map[h_errno]);
+
+	errno = h_errno = 0;
+	return ret;
+}
+
+const char *get_strerr(int optval)
+{
+	int opt = optval < 0 ? -optval : optval;
+
+	/* begin with h_errno here because it can be more descriptive for us */
+	if (h_errno)
+		return hstrerror(h_errno);
+	else if (errno)
+		return strerror(errno);
+	else if (opt)
+		return strerror(opt);
+
+	return "Unknown error";
+}
+
+void _log(FILE *f, int level, const char *fmt, ...)
+{
+	va_list va;
+
+	va_start(va, fmt);
+	if (isatty(2)) {
+		vfprintf(f, fmt, va);
+		fputc('\n', f);
+	} else {
+		vsyslog(level, fmt, va);
+	}
+	va_end(va);
+}
+
+/*
+ * Print nameserver error
+ *
+ * Error code is always h_errno (netdb.h) for these.
+ */
+void nsError(const char *hostname)
+{
+	if (isatty(2))
+		error("NS: %s: %s.", hostname, hstrerror(h_errno));
+	else
+		error("%s: %s", hostname, hstrerror(h_errno));
+}
+
+/*
+ * Print nameserver error and exit
+ */
+void nsError_ex(const char *hostname)
+{
+	unsigned timeout;
+	int ret, err;
+
+	nsError(hostname);
+
+	if (!debug_mode) {
+		if (h_errno >= ns_errno_max)
+			err = ECONNREFUSED;
+		else
+			err = ns_errno_map[h_errno];
+
+		switch (h_errno) {
+		case TRY_AGAIN:
+			timeout = 1;
+			break;
+		case 0:
+		case NO_RECOVERY:
+			timeout = 10;
+			break;
+		default:
+			timeout = 1 * 60;
+			break;
+		}
+
+		error("Reject the key with error %d", err);
+
+		ret = keyctl_reject(key, timeout, err, KEY_REQKEY_DEFL_DEFAULT);
+		if (ret == -1)
+			error_ex("keyctl_reject: %m");
+	}
+
+	exit(h_errno);
+}
+
+/*
+ * Dump the payload when debugging
+ */
+void dump_payload(payload_t *payload)
+{
+	size_t plen, n;
+	unsigned char *buf, *p;
+	int i;
+
+	plen = 0;
+	for (i = 0; i < payload->index; i++) {
+		n = payload->data[i].iov_len;
+		debug("seg[%d] size %zu", i, n);
+		plen += n;
+	}
+	if (plen == 0) {
+		info("Key instantiation data is empty");
+		return;
+	}
+
+	debug("payload len: %zu", plen);
+
+	CALLOC_CHECK(buf, 1, plen + 1);
+
+	p = buf;
+	for (i = 0; i < payload->index; i++) {
+		n = payload->data[i].iov_len;
+		memcpy(p, payload->data[i].iov_base, n);
+		p += n;
+	}
+
+	info("The key instantiation data is '%s'", buf);
+	free(buf);
+}
+
+static char *get_addr_str(int af, char *s, const void *data)
+{
+	switch (af) {
+	case AF_INET:
+		inet_ntop(AF_INET, data, s, INET_ADDRSTRLEN);
+		break;
+	case AF_INET6:
+		inet_ntop(AF_INET6, data, s, INET6_ADDRSTRLEN);
+		break;
+	default:
+		error("Invalid address family '%d'", af);
+		s = NULL;
+	}
+
+	return s;
+}
+
+/*
+ * "valid" for our cases
+ * ns_t_any is used to represent an "A/AAAA" query, i.e. both IPv4 and IPv6
+ * addresses must be queried
+ */
+static bool is_type_valid(ns_type type)
+{
+	switch(type) {
+	case ns_t_a:
+	case ns_t_aaaa:
+	case ns_t_any:
+	case ns_t_afsdb:
+	case ns_t_srv:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static inline bool is_type_resolvable(ns_type type)
+{
+	return (type == ns_t_a || type == ns_t_aaaa);
+}
+
+static bool can_resolve_host(hostinfo_t *host)
+{
+	bool afok = false, typeok = false;
+
+	if (!host)
+		return false;
+
+	switch (host->af) {
+	case AF_INET:
+	case AF_INET6:
+	case AF_UNSPEC:
+		afok = true;
+		break;
+	default:
+		afok = false;
+		break;
+	}
+
+	switch(host->type) {
+	case ns_t_a:
+	case ns_t_aaaa:
+	case ns_t_any:
+		typeok = true;
+		break;
+	default:
+		typeok = false;
+	}
+
+	return (afok && typeok);
+}
+
+static inline int ns2af(ns_type type)
+{
+	if (type == ns_t_a)
+		return AF_INET;
+	if (type == ns_t_aaaa)
+		return AF_INET6;
+
+	return AF_UNSPEC;
+}
+
+static inline void add_to_payload(payload_t *payload, void *data, size_t len)
+{
+	payload->data[payload->index].iov_base = data;
+	payload->data[payload->index].iov_len = len;
+	payload->index++;
+}
+
+/*
+ * Append an address to the payload segment list
+ */
+static void append_addr(char *addr, payload_t *payload)
+{
+	size_t len;
+	int i;
+
+	if (!addr) {
+		error("no address to append to payload");
+		return;
+	}
+
+	if (!payload) {
+		error("payload buffer is NULL, can't append addr '%s'", addr);
+		return;
+	}
+
+	len = strlen(addr);
+
+	debug("append '%s'", addr);
+
+	if (payload->index + 2 > MAX_PAYLOAD - 1) {
+		info("payload buffer is full, can't append addr '%s'", addr);
+		return;
+	}
+
+	/* do not append duplicate entry */
+	for (i = 0; i < payload->index; i++)
+		if (payload->data[i].iov_len == len &&
+		    memcmp(payload->data[i].iov_base, addr, len) == 0)
+			return;
+
+	if (payload->index != 0)
+		add_to_payload(payload, ",", 1);
+
+	add_to_payload(payload, (void *)strndup(addr, len), len);
+}
+
+/*
+ * Returns the smallest TTL from all targets.
+ */
+static long append_addrs(hostinfo_t **targets, int ntgts, payload_t *payload)
+{
+	int i;
+	long ttl = LONG_MAX;
+
+	for (i = 0; i < ntgts; i++) {
+		hostinfo_t *t = targets[i];
+		int n;
+
+		for (n = 0; n < t->naddrs; n++) {
+			if (t->port[0] != '\0')
+				strcat(t->addrs[n], t->port);
+			append_addr(t->addrs[n], payload);
+		}
+
+		dump_host(t);
+		ttl = MIN(ttl, t->ttl);
+	}
+
+	/* must include a NUL char at the end of the payload */
+	add_to_payload(payload, "", 1);
+
+	return ttl;
+}
+
+static inline bool is_host_dup(hostinfo_t *h1, hostinfo_t *h2)
+{
+	int len;
+
+	if (!h1 || !h2)
+		return false;
+
+	if (!h1->hostname || !h2->hostname)
+		return false;
+
+	len = MAX(strlen(h1->hostname), strlen(h2->hostname));
+	if (strncasecmp(h1->hostname, h2->hostname, len) != 0)
+		return false;
+
+	debug("dup host '%s'", h1->hostname);
+	return true;
+}
+
+/*
+ * Get maximum number of targets a record might have
+ */
+static int get_rr_count(ns_msg *handle)
+{
+	return ns_msg_count(*handle, ns_s_an);
+}
+
+static int get_rr_name(hostinfo_t *host, const unsigned char *rdata)
+{
+	int ret = 0;
+
+	CALLOC_CHECK(host->hostname, 1, MAXDNAME);
+
+	/* Expand the name server's domain name */
+	ret = ns_name_uncompress(ns_msg_base(*(host->handle)),
+				 ns_msg_end(*(host->handle)),
+				 rdata, host->hostname, MAXDNAME);
+	if (ret < 0) {
+		warn("ns_name_uncompress() failed: %m");
+		ret = get_err(-ENODATA);
+		return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * Parses a resource record
+ */
+static int parse_rr(hostinfo_t *host, ns_rr rr)
+{
+	const unsigned char *rdata;
+	unsigned short prio, weight, port; /* for ns_t_srv */
+	int subtype = 0; /* for ns_t_afsdb */
+	ns_type rrtype;
+	int ret = 0;
+
+	if (!host)
+		return -EINVAL;
+
+	rrtype = ns_rr_type(rr);
+	rdata = ns_rr_rdata(rr);
+
+	/* increment rdata after reading each field, based on type */
+	switch (rrtype) {
+	case ns_t_afsdb:
+		NS_GET16(subtype, rdata);
+		debug("rdata: subtype=%d", subtype);
+		break;
+	case ns_t_srv:
+		NS_GET16(prio, rdata);
+		NS_GET16(weight, rdata);
+		NS_GET16(port, rdata);
+
+		snprintf(host->port, 10, "+%hu", port); // '+' + 8 + NUL
+
+		debug("rdata: prio=%u, weight=%u, port=%u",
+		      prio, weight, port);
+		break;
+	case ns_t_a:
+	case ns_t_aaaa:
+		if (host->naddrs == MAX_ADDRS) {
+			warn("Can't add more IP addresses (max '%d' reached)",
+			     MAX_ADDRS);
+			break;
+		}
+
+		CALLOC_CHECK(host->addrs[host->naddrs], 1, MAX_ADDR_LEN);
+		get_addr_str(ns2af(rrtype), host->addrs[host->naddrs], rdata);
+
+		debug("rdata: addr=%s", host->addrs[host->naddrs]);
+
+		host->naddrs++;
+		break;
+	default:
+		debug("invalid type '%s' (%d)", str_type(rrtype), rrtype);
+		return -EINVAL;
+	}
+
+	debug("rdata: type='%s' (%d), ttl=%d", str_type(rrtype), rrtype,
+	      ns_rr_ttl(rr));
+
+	host->type = rrtype;
+	host->ttl = ns_rr_ttl(rr);
+
+	if (!is_type_resolvable(rrtype))
+		ret = get_rr_name(host, rdata);
+
+	debug("rdata: hostname='%s'", host->hostname);
+
+	return ret;
+}
+
+/*
+ * Get host targets/cells for AFSDB and SRV records
+ *
+ * @host: host with queried handle
+ * @targets: array to store targets' information. Must be initialized and
+ *	     freed by caller.
+ * @maxn: Maximum number of targets we have available to process. It gets updated
+ *        when done parsing to the actual number of targets available.
+ *
+ * Non-unique info is copied from @host to each target. Targets with duplicate
+ * hostnames are discarded.
+ *
+ * Returns 0 on success, or -error otherwise.
+ */
+static int get_targets(hostinfo_t *host, hostinfo_t **targets, int *maxn)
+{
+	int n = 0; /* list index */
+	int i, ret;
+	ns_rr rr;
+
+	debug("Resource record count is %d", *maxn);
+
+	for (i = 0; i < *maxn; i++) {
+		hostinfo_t *target = NULL;
+
+		if (ns_parserr(host->handle, ns_s_an, i, &rr)) {
+			error("ns_parserr failed: %m");
+			continue;
+		}
+
+		CALLOC_CHECK(targets[n], 1, sizeof(hostinfo_t));
+		target = targets[n];
+
+		target->handle = host->handle; /* temp */
+		ret = parse_rr(target, rr);
+		if (ret)
+			goto out;
+
+		/* discard if duplicate */
+		for (i = 0; i < n; i++)
+			if (is_host_dup(targets[i], target))
+				goto next;
+
+		target->handle = NULL;
+		target->af = host->af;
+		target->single_addr = host->single_addr;
+
+		n++;
+		continue;
+next:
+		free_host(target);
+		target = NULL;
+	}
+
+	ret = 0;
+
+	if (n == 0) {
+		error("Failed to parse all records");
+		ret = -ENODATA;
+	}
+
+	*maxn = n;
+out:
+	return ret;
+}
+
+/*
+ * "resolves" a host as in: parses a host containing A/AAAA resource
+ * records, i.e. assumes @host::handle is allocated and is valid.
+ */
+static int resolve_host(hostinfo_t *host, int n)
+{
+	int i, ret = 0, err = 0;
+	ns_rr rr;
+
+	if (!host->handle)
+		return -ENODATA;
+
+	for (i = 0; i < n; i++) {
+		ns_type type;
+
+		if (ns_parserr(host->handle, ns_s_an, i, &rr)) {
+			error("ns_parserr failed: %m");
+			continue;
+		}
+
+		type = ns_rr_type(rr);
+		/* ignore resource records that doesn't contain IP addresses */
+		if (!is_type_resolvable(type)) {
+			debug("skipping unresolvable type '%s' (%d)",
+			      str_type(type), type);
+			err++;
+			continue;
+		}
+
+		ret = parse_rr(host, rr);
+		if (ret) {
+			warn("Failed to parse host '%s': %s", host->hostname,
+			     get_strerr(ret));
+			err++;
+			/* continue */
+		}
+	}
+
+	if (err >= n)
+		ret = -ENODATA;
+
+	return ret;
+}
+
+/*
+ * Makes the actual query
+ *
+ * @host: host info to query for
+ *
+ * Returns 0 on success and sets host->handle for resource record processing. 
+ * Returns -error otherwise.
+ *
+ * Caller is responsible for freeing the allocated handle in both cases.
+ */
+static int dns_query(hostinfo_t *host)
+{
+	res_state sp;
+	int ret, len;
+
+	if (!host)
+		return -EINVAL;
+
+	if (!is_type_valid(host->type))
+		return -EINVAL;
+
+	union {
+		HEADER hdr;
+		u_char buf[NS_PACKETSZ];
+	} answer; /* answer buffers */
+
+	CALLOC_CHECK(sp, 1, sizeof(*sp));
+
+	if (res_ninit(sp) < 0) {
+		error("Can't initialize sp");
+		return -ENODEV;
+	}
+
+	h_errno = 0;
+	/* query the dns for a @type resource record */
+	len = res_nquery(sp, host->hostname, ns_c_in, host->type, answer.buf,
+			 sizeof(answer));
+
+	if (len < 0 || len > NS_MAXMSG) {
+		ret = get_err(-ENODATA);
+		goto out;
+	}
+
+	CALLOC_CHECK(host->handle, 1, sizeof(ns_msg));
+
+	ret = 0;
+
+	if (ns_initparse(answer.buf, len, host->handle) < 0) {
+		error("ns_initparse: %m");
+		ret = get_err(-ENODATA);
+	}
+out:
+	/* frees sp */
+	res_nclose(sp);
+	return ret;
+}
+
+/*
+ * Queries the host and returns the number of targets (resource records)
+ * for it. Returns -error in case of errors.
+ */
+static int query_host(hostinfo_t *host)
+{
+	int n, ret;
+
+	ret = dns_query(host);
+	if (ret) {
+		if (host->handle) {
+			free(host->handle);
+			host->handle = NULL;
+		}
+		return ret;
+	}
+
+	n = get_rr_count(host->handle);
+	if (n == 0)
+		return -ENODATA;
+
+	if (!is_type_resolvable(host->type) && n > MAX_VLS) {
+		info("Processing only '%d' records.", MAX_VLS);
+		return MAX_VLS;
+	}
+
+	if (n > 1 && is_type_resolvable(host->type) && host->single_addr)
+		return 1;
+
+	return n;
+}
+
+static int __resolve_targets(hostinfo_t **targets, int ntgts, ns_type type)
+{
+	int n = 0;
+	int i, ret, err = 0;
+
+	if (!is_type_resolvable(type))
+		return -EINVAL;
+
+	for (i = 0; i < ntgts; i++) {
+		targets[i]->type = type;
+		targets[i]->af = ns2af(type);
+
+		n = query_host(targets[i]);
+		if (!n) {
+			debug("can't query '%s', error %d", targets[i]->hostname, n);
+			err++;
+			continue;
+		}
+
+		ret = resolve_host(targets[i], n);
+		if (ret) {
+			debug("failed to parse '%s' resource records for target '%s': %s",
+			      str_type(type), targets[i]->hostname, get_strerr(ret));
+			err++;
+		}
+	}
+
+	if (err >= ntgts)
+		ret = -ENODATA;
+
+	return ret;
+}
+
+static int resolve_targets4(hostinfo_t **targets, int ntgts)
+{
+	return __resolve_targets(targets, ntgts, ns_t_a);
+}
+
+static int resolve_targets6(hostinfo_t **targets, int ntgts)
+{
+	return __resolve_targets(targets, ntgts, ns_t_aaaa);
+}
+
+/*
+ * This function resolves the targets in @targets, based on @af address family
+ * (requested by the "ipv4" or "ipv6" options).
+ *
+ * On success, each target will have at least one IP address in its addrs list,
+ * ready to be appended to the payload. Returns 0.
+ * On errors:
+ * - if @af is AF_INET or AF_INET6, returns -ENODATA
+ * - if @af is AF_UNSPEC, returns -AF_INET if only ipv4 failed, -AF_INET6 if
+ *   only ipv6 failed, and -ENODATA if both failed
+ */
+static int resolve_targets(hostinfo_t **targets, int ntgts, int af)
+{
+	int ret4 = 0, ret6 = 0, ret = 0;
+
+	if (IS_IP4(af) || IS_IP_ANY(af))
+		ret4 = resolve_targets4(targets, ntgts);
+	if (ret4) {
+		warn("Failed to resolve IPv4 targets: %s", get_strerr(ret4));
+		ret = IS_IP_ANY(af) ? -AF_INET : -ENODATA;
+	}
+
+	if (IS_IP6(af) || IS_IP_ANY(af))
+		ret6 = resolve_targets6(targets, ntgts);
+	if (ret6) {
+		warn("Failed to resolve IPv6 targets: %s", get_strerr(ret6));
+		ret = IS_IP_ANY(af) ? -AF_INET6 : -ENODATA;
+	}
+
+	if (ret4 && ret6)
+		ret = -ENODATA;
+
+	return ret;
+}
+
+/*
+ * Perform address resolution for a hostname and add the resulting addresses as
+ * strings to the list of payload segments
+ *
+ * @host: host information to query for
+ * @payload: payload buffer to append results to. Initialized and freed by caller.
+ *
+ * Returns 0 on success, -error otherwise.
+ */
+int dns_resolver(hostinfo_t *host, payload_t *payload)
+{
+	hostinfo_t *targets[MAX_TARGETS];
+	int ntgts = 0, i;
+	int ret = 0;
+	long ttl;
+
+	if (!host || !host->hostname || !payload)
+		return -EINVAL;
+
+	if (!is_type_valid(host->type)) {
+		warn("Invalid query type '%s' (%d)", str_type(host->type),
+		     host->type);
+		return -EINVAL;
+	}
+
+	debug("Querying hostname %s with query type '%s' (%d)",
+	      host->hostname, str_type(host->type), host->type);
+
+	for (i = 0; i < MAX_TARGETS; i++)
+		targets[i] = NULL;
+
+	host->handle = NULL;
+	host->naddrs = 0;
+
+	if (can_resolve_host(host)) {
+		targets[0] = host;
+		ntgts = 1;
+
+		goto resolve;
+	}
+
+	/* else, can't straight resolve, must query+fetch targets first */
+	ntgts = query_host(host);
+	if (ntgts < 1) {
+		ret = ntgts;
+		error("Failed to query host '%s': %s", host->hostname,
+		      get_strerr(ret));
+		goto out;
+	}
+
+	ret = get_targets(host, targets, &ntgts);
+	if (ret || ntgts == 0) {
+		error("Failed to get targets for '%s': %s", host->hostname,
+		      get_strerr(ret));
+		goto out;
+	}
+
+resolve:
+	ret = resolve_targets(targets, ntgts, host->af);
+	if (ret == -ENODATA) {
+		error("Failed to resolve targets for '%s': %s", host->hostname,
+		      get_strerr(ret));
+		goto out;
+	} /* else, we got at least *some* data */
+
+	/* append resolved addresses to payload */
+	ttl = append_addrs(targets, ntgts, payload);
+
+	host->ttl = clamp(ttl, -1, LONG_MAX);
+
+	/* success if reached here */
+	ret = errno = h_errno = 0;
+out:
+	i = 0;
+	while (targets[i] && targets[i] != host)
+		free_host(targets[i++]);
+
+	return ret;
+}
diff --git a/key.dns.h b/key.dns.h
index 33d0ab3b05b1..f522a51b92ce 100644
--- a/key.dns.h
+++ b/key.dns.h
@@ -1,14 +1,25 @@
 /*
+ * Common DNS resolving code for keyutils
+ *
+ * Copyright (c) 2022, SUSE LLC
+ * Author: Enzo Matsumiya <ematsumiya@suse.de>
+ *
  * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public Licence as published by
- * the Free Software Foundation; either version 2 of the Licence, or
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public Licence for more details.
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  */
+#ifndef _KEY_DNS_H
+#define _KEY_DNS_H 
+
 #define _GNU_SOURCE
 #include <netinet/in.h>
 #include <arpa/nameser.h>
@@ -25,50 +36,216 @@
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdarg.h>
-#include <keyutils.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <time.h>
 #include <ctype.h>
+#include <sys/param.h>
+#include <sys/uio.h> // struct iovec
+
+#include "keyutils.h"
+
+extern int verbose;
+extern int debug_mode;
+extern key_serial_t key;
+extern long key_expiry;
+
+#define DEFAULT_KEY_TTL 5
+
+#define MAX_VLS		15	/* Max Volume Location Servers Per-Cell (AFSDB) */
+#define MAX_PAYLOAD	256	/* Max number of payload vectors (iovecs) */
 
-#define	MAX_VLS			15	/* Max Volume Location Servers Per-Cell */
-#define	INET_IP4_ONLY		0x1
-#define	INET_IP6_ONLY		0x2
-#define	INET_ALL		0xFF
-#define ONE_ADDR_ONLY		0x100
+#define MAX_ADDRS	MAX_PAYLOAD	/* Max number of IP addresses for a hostname */
+#define MAX_ADDR_LEN	(INET6_ADDRSTRLEN + 8 + 1)
+#define MAX_TARGETS	128
+
+#define IS_IP4(af)	((af) == AF_INET)
+#define IS_IP6(af)	((af) == AF_INET6)
+#define IS_IP_ANY(af)	((af) == AF_UNSPEC)
+
+/* For DNS/libresolv-specific messages */
+void nsError(const char *hostname);
+void nsError_ex(const char *hostname);
+int get_err(int optval);
+/* For keyutils-specific messages */
+extern __attribute__((format(printf, 3, 4)))
+void _log(FILE *f, int level, const char *fmt, ...);
 
 /*
- * key.dns_resolver.c
+ * Just print an error to stderr or the syslog
  */
-extern key_serial_t key;
-extern int debug_mode;
-extern unsigned mask;
-extern unsigned int key_expiry;
-
-#define N_PAYLOAD 256
-extern struct iovec payload[N_PAYLOAD];
-extern int payload_index;
-
-extern __attribute__((format(printf, 1, 2), noreturn))
-void error(const char *fmt, ...);
-extern __attribute__((format(printf, 1, 2)))
-void _error(const char *fmt, ...);
-extern __attribute__((format(printf, 1, 2)))
-void warning(const char *fmt, ...);
-extern __attribute__((format(printf, 1, 2)))
-void info(const char *fmt, ...);
-extern __attribute__((noreturn))
-void nsError(int err, const char *domain);
-extern void _nsError(int err, const char *domain);
-extern __attribute__((format(printf, 1, 2)))
-void debug(const char *fmt, ...);
-
-extern void append_address_to_payload(const char *addr);
-extern void dump_payload(void);
-extern int dns_resolver(const char *server_name, const char *port);
+#define error(fmt, ...) \
+	do { \
+		_log(stderr, LOG_ERR, "E: " fmt, ##__VA_ARGS__); \
+	} while (0)
+
+/*
+ * Print an error to stderr or the syslog, negate the key being created, and
+ * exit with a generic -1 error code.
+ *
+ * On error, negatively instantiate the key ourselves so that we can
+ * make sure the kernel doesn't hang it off of a searchable keyring
+ * and interfere with the next attempt to instantiate the key.
+ */
+#define error_ex(fmt, ...) \
+	do { \
+		_log(stderr, LOG_ERR, fmt, ##__VA_ARGS__); \
+		if (!debug_mode) \
+			keyctl_negate(key, 1, KEY_REQKEY_DEFL_DEFAULT); \
+		exit(-1); \
+	} while (0)
+
+#define error_oom() error_ex("%s: out of memory", __func__)
+
+/*
+ * Print a warning to stderr or the syslog
+ */
+#define warn(fmt, ...) \
+	do { \
+		_log(stderr, LOG_WARNING, "W: " fmt, ##__VA_ARGS__); \
+	} while (0)
+
+/*
+ * Print status information
+ */
+#define info(fmt, ...) \
+	do { \
+		if (verbose >= 1) \
+			_log(stdout, LOG_INFO, "I: " fmt, ##__VA_ARGS__); \
+	} while (0)
 
 /*
- * dns.afsdb.c
+ * Print debugging information
  */
-extern __attribute__((noreturn))
-void afs_look_up_VL_servers(const char *cell, char *options);
+#define debug(fmt, ...) \
+	do { \
+		if (verbose >= 2) \
+			_log(stdout, LOG_DEBUG, "D: " fmt, ##__VA_ARGS__); \
+	} while (0)
+
+/*
+ * Print nameserver warning
+ */
+#define nsWarn(fmt, ...) \
+	do { \
+		if (isatty(2)) \
+			_log(stderr, LOG_WARNING, "NS: W: " fmt, ##__VA_ARGS__); \
+		else \
+			_log(stderr, LOG_WARNING, fmt, ##__VA_ARGS__); \
+	} while (0)
+
+#define STRNDUP_CHECK(dst, src, len) \
+	do { \
+		(dst) = strndup(src, len); \
+		if (!(dst)) \
+			error_oom(); \
+	} while (0)
+
+#define CALLOC_CHECK(dst, count, size) \
+	do { \
+		(dst) = calloc(count, size); \
+		if (!(dst)) \
+			error_oom(); \
+	} while (0)
+
+static inline const char *str_type(ns_type type)
+{
+	if (type == ns_t_invalid)
+		return "invalid";
+
+	if (type == ns_t_any)
+		return "A/AAAA";
+
+	/* p_type is deprecated in glibc >2.34 */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+	return p_type(type);
+#pragma GCC diagnostic pop
+}
+
+/* data to be sent through keyctl */
+struct payload {
+	struct iovec data[MAX_PAYLOAD];
+	int index;
+};
+
+typedef struct payload payload_t;
+
+/* Structure to easily pass around host information */
+struct hostinfo {
+	char *hostname;
+	ns_type type;
+	char *addrs[MAX_ADDRS];
+	int naddrs;
+	char port[10];
+	int af;
+	long ttl;
+	ns_msg *handle;
+	bool single_addr;
+};
+
+typedef struct hostinfo hostinfo_t;
+
+static inline void dump_host(hostinfo_t *host)
+{
+	int i = 0;
+
+	if (!host)
+		return;
+
+	debug("Host info:");
+	debug("  hostname: %s", host->hostname);
+	debug("  type: %s", (host->type == ns_t_a || host->type == ns_t_aaaa) ?
+			    "A/AAAA" : str_type(host->type));
+	for (i = 0; i < host->naddrs; i++)
+		debug("  addr[%d]: %s", i, host->addrs[i]);
+	if (host->port[0] != '\0')
+		debug("  port: %s", host->port+1);
+	/* skip af; it's used only internally for resolving and could contain
+	 * incoherent information */
+	debug("  ttl: %ld", host->ttl);
+}
+
+static inline void free_hostinfo(hostinfo_t *host)
+{
+	int i;
+
+	if (!host)
+		return;
+
+	if (host->hostname) {
+		free(host->hostname);
+		host->hostname = NULL;
+	}
+	for (i = 0; i < host->naddrs; i++) {
+		if (host->addrs[i]) {
+			free(host->addrs[i]);
+			host->addrs[i] = NULL;
+		}
+	}
+	if (host->handle) {
+		free(host->handle);
+		host->handle = NULL;
+	}
+}
+
+static inline void free_host(hostinfo_t *host)
+{
+	if (!host)
+		return;
+
+	free_hostinfo(host);
+	free(host);
+	host = NULL;
+}
+
+extern void parse_opts(hostinfo_t *host, char *options);
+void dump_payload(payload_t *payload);
+
+/* Main function for DNS module */
+int dns_resolver(hostinfo_t *host, payload_t *payload);
+
+/* AFS-specific DNS query function */
+void afs_lookup_VL_servers(const char *cell, char *options);
+
+#endif /* _KEY_DNS_H */
diff --git a/key.dns_resolver.c b/key.dns_resolver.c
index 7a7ec4241ea0..0c680caf98cc 100644
--- a/key.dns_resolver.c
+++ b/key.dns_resolver.c
@@ -49,399 +49,138 @@ static const char afsdb_query_type[] = "afsdb";
 static const char *config_file = "/etc/keyutils/key.dns_resolver.conf";
 static bool config_specified = false;
 key_serial_t key;
-static int verbose;
+int verbose;
 int debug_mode;
-unsigned mask = INET_ALL;
-unsigned int key_expiry = 5;
+long key_expiry = DEFAULT_KEY_TTL;
 
-
-/*
- * segmental payload
- */
-struct iovec payload[N_PAYLOAD];
-int payload_index;
-
-/*
- * Print an error to stderr or the syslog, negate the key being created and
- * exit
- */
-void error(const char *fmt, ...)
-{
-	va_list va;
-
-	va_start(va, fmt);
-	if (isatty(2)) {
-		vfprintf(stderr, fmt, va);
-		fputc('\n', stderr);
-	} else {
-		vsyslog(LOG_ERR, fmt, va);
-	}
-	va_end(va);
-
-	/*
-	 * on error, negatively instantiate the key ourselves so that we can
-	 * make sure the kernel doesn't hang it off of a searchable keyring
-	 * and interfere with the next attempt to instantiate the key.
-	 */
-	if (!debug_mode)
-		keyctl_negate(key, 1, KEY_REQKEY_DEFL_DEFAULT);
-
-	exit(1);
-}
-
-#define error(FMT, ...) error("Error: " FMT, ##__VA_ARGS__);
-
-/*
- * Just print an error to stderr or the syslog
- */
-void _error(const char *fmt, ...)
-{
-	va_list va;
-
-	va_start(va, fmt);
-	if (isatty(2)) {
-		vfprintf(stderr, fmt, va);
-		fputc('\n', stderr);
-	} else {
-		vsyslog(LOG_ERR, fmt, va);
-	}
-	va_end(va);
-}
-
-/*
- * Print a warning to stderr or the syslog
- */
-void warning(const char *fmt, ...)
-{
-	va_list va;
-
-	va_start(va, fmt);
-	if (isatty(2)) {
-		vfprintf(stderr, fmt, va);
-		fputc('\n', stderr);
-	} else {
-		vsyslog(LOG_WARNING, fmt, va);
-	}
-	va_end(va);
-}
-
-/*
- * Print status information
- */
-void info(const char *fmt, ...)
-{
-	va_list va;
-
-	if (verbose < 1)
-		return;
-
-	va_start(va, fmt);
-	if (isatty(1)) {
-		fputs("I: ", stdout);
-		vfprintf(stdout, fmt, va);
-		fputc('\n', stdout);
-	} else {
-		vsyslog(LOG_INFO, fmt, va);
-	}
-	va_end(va);
-}
-
-/*
- * Print a nameserver error and exit
- */
-static const int ns_errno_map[] = {
-	[0]			= ECONNREFUSED,
-	[HOST_NOT_FOUND]	= ENODATA,
-	[TRY_AGAIN]		= EAGAIN,
-	[NO_RECOVERY]		= ECONNREFUSED,
-	[NO_DATA]		= ENODATA,
-};
-
-void _nsError(int err, const char *domain)
-{
-	if (isatty(2))
-		fprintf(stderr, "NS:%s: %s.\n", domain, hstrerror(err));
-	else
-		syslog(LOG_INFO, "%s: %s", domain, hstrerror(err));
-
-	if (err >= sizeof(ns_errno_map) / sizeof(ns_errno_map[0]))
-		err = ECONNREFUSED;
-	else
-		err = ns_errno_map[err];
-
-	info("Reject the key with error %d", err);
-}
-
-void nsError(int err, const char *domain)
-{
-	unsigned timeout;
-	int ret;
-
-	_nsError(err, domain);
-
-	switch (err) {
-	case TRY_AGAIN:
-		timeout = 1;
-		break;
-	case 0:
-	case NO_RECOVERY:
-		timeout = 10;
-		break;
-	default:
-		timeout = 1 * 60;
-		break;
-	}
-
-	if (!debug_mode) {
-		ret = keyctl_reject(key, timeout, err, KEY_REQKEY_DEFL_DEFAULT);
-		if (ret == -1)
-			error("%s: keyctl_reject: %m", __func__);
-	}
-	exit(0);
-}
-
-/*
- * Print debugging information
- */
-void debug(const char *fmt, ...)
+void parse_opts(hostinfo_t *host, char *options)
 {
-	va_list va;
+	char *k, *val;
+	bool invalid;
 
-	if (verbose < 2)
+	if (!host || !options)
 		return;
 
-	va_start(va, fmt);
-	if (isatty(1)) {
-		fputs("D: ", stdout);
-		vfprintf(stdout, fmt, va);
-		fputc('\n', stdout);
-	} else {
-		vsyslog(LOG_DEBUG, fmt, va);
-	}
-	va_end(va);
-}
-
-/*
- * Append an address to the payload segment list
- */
-void append_address_to_payload(const char *addr)
-{
-	size_t sz = strlen(addr);
-	char *copy;
-	int loop;
-
-	debug("append '%s'", addr);
-
-	if (payload_index + 2 > N_PAYLOAD - 1)
-		return;
-
-	/* discard duplicates */
-	for (loop = 0; loop < payload_index; loop++)
-		if (payload[loop].iov_len == sz &&
-		    memcmp(payload[loop].iov_base, addr, sz) == 0)
-			return;
-
-	copy = malloc(sz);
-	if (!copy)
-		error("%s: malloc: %m", __func__);
-	memcpy(copy, addr, sz);
-
-	if (payload_index != 0) {
-		payload[payload_index  ].iov_base = ",";
-		payload[payload_index++].iov_len = 1;
-	}
-	payload[payload_index  ].iov_base = copy;
-	payload[payload_index++].iov_len = sz;
-}
-
-/*
- * Dump the payload when debugging
- */
-void dump_payload(void)
-{
-	size_t plen, n;
-	char *buf, *p;
-	int loop;
-
-	if (debug_mode)
-		verbose = 1;
-	if (verbose < 1)
-		return;
-
-	plen = 0;
-	for (loop = 0; loop < payload_index; loop++) {
-		n = payload[loop].iov_len;
-		debug("seg[%d]: %zu", loop, n);
-		plen += n;
-	}
-	if (plen == 0) {
-		info("The key instantiation data is empty");
-		return;
-	}
-
-	debug("total: %zu", plen);
-	buf = malloc(plen + 1);
-	if (!buf)
-		return;
-
-	p = buf;
-	for (loop = 0; loop < payload_index; loop++) {
-		n = payload[loop].iov_len;
-		memcpy(p, payload[loop].iov_base, n);
-		p += n;
-	}
-
-	info("The key instantiation data is '%s'", buf);
-	info("The expiry time is %us", key_expiry);
-	free(buf);
-}
-
-/*
- * Perform address resolution on a hostname and add the resulting address as a
- * string to the list of payload segments.
- */
-int dns_resolver(const char *server_name, const char *port)
-{
-	struct addrinfo hints, *addr, *ai;
-	char buf[INET6_ADDRSTRLEN + 8 + 1];
-	int ret, len;
-	void *sa;
-
-	debug("Resolve '%s' with %x", server_name, mask);
-
-	memset(&hints, 0, sizeof(hints));
-	switch (mask & INET_ALL) {
-	case INET_IP4_ONLY:	hints.ai_family = AF_INET;	debug("IPv4"); break;
-	case INET_IP6_ONLY:	hints.ai_family = AF_INET6;	debug("IPv6"); break;
-	default: break;
-	}
-
-	/* resolve name to ip */
-	ret = getaddrinfo(server_name, NULL, &hints, &addr);
-	if (ret) {
-		info("unable to resolve hostname: %s [%s]",
-		     server_name, gai_strerror(ret));
-		return -1;
-	}
-
-	for (ai = addr; ai; ai = ai->ai_next) {
-		debug("RR: %x,%x,%x,%x,%x,%s",
-		      ai->ai_flags, ai->ai_family,
-		      ai->ai_socktype, ai->ai_protocol,
-		      ai->ai_addrlen, ai->ai_canonname);
-
-		/* convert address to string */
-		switch (ai->ai_family) {
-		case AF_INET:
-			if (!(mask & INET_IP4_ONLY))
-				continue;
-			sa = &(((struct sockaddr_in *)ai->ai_addr)->sin_addr);
-			len = INET_ADDRSTRLEN;
-			break;
-		case AF_INET6:
-			if (!(mask & INET_IP6_ONLY))
-				continue;
-			sa = &(((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr);
-			len = INET6_ADDRSTRLEN;
-			break;
-		default:
-			debug("Address of unknown family %u", addr->ai_family);
+	do {
+		invalid = false;
+		k = options;
+		options = strchr(options, ' ');
+		if (!options)
+			options = k + strlen(k);
+		else
+			*options++ = '\0';
+		if (!*k)
 			continue;
+		if (strchr(k, ','))
+			error_ex("Option name '%s' contains a comma", k);
+
+		val = strchr(k, '=');
+		if (val)
+			*val++ = '\0';
+
+		if (strcmp(k, "ipv4") == 0) {
+			host->af = AF_INET;
+			host->type = ns_t_a;
+		} else if (strcmp(k, "ipv6") == 0) {
+			host->af = AF_INET6;
+			host->type = ns_t_aaaa;
+		} else if (strcmp(k, "list") == 0) {
+			host->single_addr = false;
+		} else {
+			invalid = true;
 		}
 
-		if (!inet_ntop(ai->ai_family, sa, buf, len))
-			error("%s: inet_ntop: %m", __func__);
-
-		if (port)
-			strcat(buf, port);
-		append_address_to_payload(buf);
-		if (mask & ONE_ADDR_ONLY)
-			break;
-	}
-
-	freeaddrinfo(addr);
-	return 0;
+		if (invalid && !val)
+			warn("Skipping invalid opt %s", k);
+		else if (invalid && val)
+			warn("Skipping invalid opt %s=%s", k, val);
+		else if (val)
+			debug("Opt %s=%s", k, val);
+		else
+			debug("Opt %s", k);
+	} while (*options);
 }
 
 /*
  * Look up a A and/or AAAA records to get host addresses
  *
- * The callout_info is parsed for request options.  For instance, "ipv4" to
- * request only IPv4 addresses, "ipv6" to request only IPv6 addresses and
- * "list" to get multiple addresses.
+ * @hostname: hostname to query for
+ * @options is parsed for request options:
+ *   "ipv4": to request only IPv4 addresses
+ *   "ipv6": to request only IPv6 addresses
+ *   "list": to get multiple addresses
  */
 static __attribute__((noreturn))
 int dns_query_a_or_aaaa(const char *hostname, char *options)
 {
-	int ret;
+	payload_t *payload = NULL;
+	hostinfo_t host = { 0 };
+	int ret = 0;
 
-	debug("Get A/AAAA RR for hostname:'%s', options:'%s'",
-	      hostname, options);
+	if (!hostname)
+		error_ex("%s: missing hostname", __func__);
 
-	if (!options[0]) {
-		/* legacy mode */
-		mask = INET_IP4_ONLY | ONE_ADDR_ONLY;
-	} else {
-		char *k, *val;
-
-		mask = INET_ALL | ONE_ADDR_ONLY;
-
-		do {
-			k = options;
-			options = strchr(options, ' ');
-			if (!options)
-				options = k + strlen(k);
-			else
-				*options++ = '\0';
-			if (!*k)
-				continue;
-			if (strchr(k, ','))
-				error("Option name '%s' contains a comma", k);
-
-			val = strchr(k, '=');
-			if (val)
-				*val++ = '\0';
+	debug("Query A/AAAA records for hostname:'%s', options:'%s'",
+	      hostname, options);
 
-			debug("Opt %s", k);
+	host.af = AF_UNSPEC;
+	host.single_addr = true;
+	host.type = ns_t_any;
 
-			if (strcmp(k, "ipv4") == 0) {
-				mask &= ~INET_ALL;
-				mask |= INET_IP4_ONLY;
-			} else if (strcmp(k, "ipv6") == 0) {
-				mask &= ~INET_ALL;
-				mask |= INET_IP6_ONLY;
-			} else if (strcmp(k, "list") == 0) {
-				mask &= ~ONE_ADDR_ONLY;
-			}
+	parse_opts(&host, options);
 
-		} while (*options);
-	}
+	CALLOC_CHECK(payload, 1, sizeof(payload_t));
+	STRNDUP_CHECK(host.hostname, hostname, strlen(hostname));
+	ret = h_errno = 0;
 
 	/* Turn the hostname into IP addresses */
-	ret = dns_resolver(hostname, NULL);
-	if (ret)
-		nsError(NO_DATA, hostname);
+	ret = dns_resolver(&host, payload);
 
 	/* handle a lack of results */
-	if (payload_index == 0)
-		nsError(NO_DATA, hostname);
+	if (ret || payload->index == 0) {
+		ret = get_err(0);
+		goto out_free;
+	}
 
-	/* must include a NUL char at the end of the payload */
-	payload[payload_index].iov_base = "";
-	payload[payload_index++].iov_len = 1;
-	dump_payload();
+	dump_payload(payload);
+	info("Key timeout will be %ld seconds", host.ttl);
 
 	/* load the key with data key */
 	if (!debug_mode) {
-		ret = keyctl_set_timeout(key, key_expiry);
-		if (ret == -1)
+		unsigned int ttl;
+
+		/*
+		 * If TTL was set through the config file (key_expiry),
+		 * it takes precedence over the one from the DNS record (stored
+		 * in host.ttl).
+		 */
+		if (key_expiry > -1)
+			ttl = (unsigned int)key_expiry;
+		else if (host.ttl > -1)
+			ttl = (unsigned int)host.ttl;
+		else
+			/* Fallback to default value if dns_resolver() couldn't
+			 * get TTL for some reason */
+			ttl = DEFAULT_KEY_TTL;
+
+		ret = keyctl_set_timeout(key, ttl);
+		if (ret) {
 			error("%s: keyctl_set_timeout: %m", __func__);
-		ret = keyctl_instantiate_iov(key, payload, payload_index, 0);
+			goto out_free;
+		}
+
+		info("Key set to timeout in %u seconds", ttl);
+
+		ret = keyctl_instantiate_iov(key, payload->data, payload->index, 0);
 		if (ret == -1)
 			error("%s: keyctl_instantiate: %m", __func__);
 	}
 
-	exit(0);
+out_free:
+	free_hostinfo(&host);
+	free(payload);
+
+	exit(ret);
 }
 
 /*
@@ -451,7 +190,8 @@ static void read_config(void)
 {
 	FILE *f;
 	char buf[4096], *b, *p, *k, *v;
-	unsigned int line = 0, u;
+	unsigned int line = 0;
+	long u;
 	int n;
 
 	info("READ CONFIG %s", config_file);
@@ -555,15 +295,15 @@ static void read_config(void)
 		if (strcmp(k, "default_ttl") == 0) {
 			if (!v)
 				goto missing_value;
-			if (sscanf(v, "%u%n", &u, &n) != 1)
+			if (sscanf(v, "%ld%n", &u, &n) != 1)
 				goto bad_value;
 			if (v[n])
 				goto extra_data;
-			if (u < 1 || u > INT_MAX)
+			if (u < 1 || u > LONG_MAX)
 				goto out_of_range;
 			key_expiry = u;
 		} else {
-			warning("%s:%u: Unknown option '%s'", config_file, line, k);
+			warn("%s:%u: Unknown option '%s'", config_file, line, k);
 		}
 	}
 
@@ -591,7 +331,7 @@ out_of_range:
 static __attribute__((noreturn))
 void config_dumper(void)
 {
-	printf("default_ttl = %u\n", key_expiry);
+	printf("default_ttl = %ld\n", key_expiry);
 	exit(0);
 }
 
@@ -634,6 +374,8 @@ int main(int argc, char *argv[])
 	char *buf = NULL, *name;
 	bool dump_config = false;
 
+	key_expiry = -1;
+
 	openlog(prog, 0, LOG_DAEMON);
 
 	while ((ret = getopt_long(argc, argv, "c:vDV", long_options, NULL)) != -1) {
@@ -667,6 +409,7 @@ int main(int argc, char *argv[])
 	argc -= optind;
 	argv += optind;
 	read_config();
+
 	if (dump_config)
 		config_dumper();
 
@@ -676,27 +419,27 @@ int main(int argc, char *argv[])
 
 		/* get the key ID */
 		if (!**argv)
-			error("Invalid blank key ID");
+			error_ex("Invalid blank key ID");
 		key = strtol(*argv, &p, 10);
 		if (*p)
-			error("Invalid key ID format");
+			error_ex("Invalid key ID format");
 
 		/* get the key description (of the form "x;x;x;x;<query_type>:<name>") */
 		ret = keyctl_describe_alloc(key, &buf);
 		if (ret == -1)
-			error("keyctl_describe_alloc failed: %m");
+			error_ex("keyctl_describe_alloc failed: %m");
 
 		/* get the callout_info (which can supply options) */
 		ret = keyctl_read_alloc(KEY_SPEC_REQKEY_AUTH_KEY, (void **)&callout_info);
 		if (ret == -1)
-			error("Invalid key callout_info read: %m");
+			error_ex("Invalid key callout_info read: %m");
 	} else {
 		if (argc != 2)
 			usage();
 
 		ret = asprintf(&buf, "%s;-1;-1;0;%s", key_type, argv[0]);
 		if (ret < 0)
-			error("Error %m");
+			error_ex("Error %m");
 		callout_info = argv[1];
 	}
 
@@ -706,20 +449,20 @@ int main(int argc, char *argv[])
 
 	p = strchr(buf, ';');
 	if (!p)
-		error("Badly formatted key description '%s'", buf);
+		error_ex("Badly formatted key description '%s'", buf);
 	ktlen = p - buf;
 
 	/* make sure it's the type we are expecting */
 	if (ktlen != sizeof(key_type) - 1 ||
 	    memcmp(buf, key_type, ktlen) != 0)
-		error("Key type is not supported: '%*.*s'", ktlen, ktlen, buf);
+		error_ex("Key type is not supported: '%*.*s'", ktlen, ktlen, buf);
 
 	keyend = buf + ktlen + 1;
 
 	/* the actual key description follows the last semicolon */
 	keyend = rindex(keyend, ';');
 	if (!keyend)
-		error("Invalid key description: %s", buf);
+		error_ex("Invalid key description: %s", buf);
 	keyend++;
 
 	name = index(keyend, ':');
@@ -736,7 +479,7 @@ int main(int argc, char *argv[])
 	    (qtlen == sizeof(aaaa_query_type) - 1 &&
 	     memcmp(keyend, aaaa_query_type, sizeof(aaaa_query_type) - 1) == 0)
 	    ) {
-		info("Do DNS query of A/AAAA type for:'%s' mask:'%s'",
+		info("Do DNS query of A/AAAA type for '%s', with options '%s'",
 		     name, callout_info);
 		dns_query_a_or_aaaa(name, callout_info);
 	}
@@ -744,10 +487,11 @@ int main(int argc, char *argv[])
 	if (qtlen == sizeof(afsdb_query_type) - 1 &&
 	    memcmp(keyend, afsdb_query_type, sizeof(afsdb_query_type) - 1) == 0
 	    ) {
-		info("Do AFS VL server query for:'%s' mask:'%s'",
+		info("Do AFS VL server query for '%s', with options '%s'",
 		     name, callout_info);
-		afs_look_up_VL_servers(name, callout_info);
+		afs_lookup_VL_servers(name, callout_info);
 	}
 
 	error("Query type: \"%*.*s\" is not supported", qtlen, qtlen, keyend);
+	exit(-EINVAL);
 }
