All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] DNS: If the DNS server returns an error, allow that to be cached
@ 2010-08-06 18:20 ` David Howells
  0 siblings, 0 replies; 6+ messages in thread
From: David Howells @ 2010-08-06 18:20 UTC (permalink / raw)
  To: smfrench-Re5JQEeQqe8AvxtiuMwx3w, jlayton-H+wXaHxf7aLQT0dZR+AlfA
  Cc: linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-afs-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Wang Lei, David Howells

From: Wang Lei <wang840925-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>

If the DNS server returns an error, allow that to be cached in the DNS resolver
key in lieu of a value.  Userspace passes the h_errno from the libresolv
routines as an option in the payload:

	"#dnserror=<number>"

This is mapped through dns_errno_map[] to an appropriate Linux error code.  To
this end, an internal error EHOSTNOTFOUND is added to be passed back to the
caller to indicate the resultless lookup.

If this option is used, the main part of the payload is discarded.  The key may
still have an expiry time set, however.

Signed-off-by: Wang Lei <wang840925-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Signed-off-by: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---

 fs/afs/cell.c                |    5 +++
 include/linux/errno.h        |    3 ++
 net/dns_resolver/dns_key.c   |   84 ++++++++++++++++++++++++++++++++++++++++--
 net/dns_resolver/dns_query.c |    5 +++
 4 files changed, 93 insertions(+), 4 deletions(-)

diff --git a/fs/afs/cell.c b/fs/afs/cell.c
index ffea35c..acbefec 100644
--- a/fs/afs/cell.c
+++ b/fs/afs/cell.c
@@ -73,6 +73,11 @@ static struct afs_cell *afs_cell_alloc(const char *name, char *vllist)
 	if (!vllist || strlen(vllist) < 7) {
 		ret = dns_query("afsdb", name, namelen, "ipv4", &dvllist, NULL);
 		if (ret < 0) {
+			if (ret == -EHOSTNOTFOUND || ret == -EAGAIN ||
+			    ret == -ENOKEY)
+				/* translate these errors into something
+				 * userspace might understand */
+				ret = -EDESTADDRREQ;
 			_leave(" = %d", ret);
 			return ERR_PTR(ret);
 		}
diff --git a/include/linux/errno.h b/include/linux/errno.h
index 4668583..6c14a57 100644
--- a/include/linux/errno.h
+++ b/include/linux/errno.h
@@ -29,6 +29,9 @@
 #define EIOCBQUEUED	529	/* iocb queued, will get completion event */
 #define EIOCBRETRY	530	/* iocb queued, will trigger a retry */
 
+/* Defined for the DNS */
+#define EHOSTNOTFOUND	531	/* Server has no result for the request */
+
 #endif
 
 #endif
diff --git a/net/dns_resolver/dns_key.c b/net/dns_resolver/dns_key.c
index 1b1b411..c429d43 100644
--- a/net/dns_resolver/dns_key.c
+++ b/net/dns_resolver/dns_key.c
@@ -42,6 +42,18 @@ MODULE_PARM_DESC(debug, "DNS Resolver debugging mask");
 
 const struct cred *dns_resolver_cache;
 
+#define	DNS_ERRORNO_OPTION	"dnserror"
+
+static const int dns_errno_map[] = {
+	0,			/* no error */
+	-EHOSTNOTFOUND,		/* handle h_errno HOST_NOT_FOUND = 1 */
+	-EAGAIN,		/* handle h_errno TRY_AGAIN = 2 */
+	-ECONNREFUSED,		/* handle h_errno NO_RECOVERY = 3 */
+	-EHOSTNOTFOUND,		/* handle h_errno NO_DATA = 4 */
+};
+
+#define ERRNO_MAP_MAX  (ARRAY_SIZE(dns_errno_map) - 1)
+
 /*
  * Instantiate a user defined key for dns_resolver.
  *
@@ -58,9 +70,10 @@ static int
 dns_resolver_instantiate(struct key *key, const void *_data, size_t datalen)
 {
 	struct user_key_payload *upayload;
+	unsigned long derrno;
 	int ret;
 	size_t result_len = 0;
-	const char *data = _data, *opt;
+	const char *data = _data, *end, *opt;
 
 	kenter("%%%d,%s,'%s',%zu",
 	       key->serial, key->description, data, datalen);
@@ -70,13 +83,76 @@ dns_resolver_instantiate(struct key *key, const void *_data, size_t datalen)
 	datalen--;
 
 	/* deal with any options embedded in the data */
+	end = data + datalen;
 	opt = memchr(data, '#', datalen);
 	if (!opt) {
-		kdebug("no options currently supported");
-		return -EINVAL;
+		/* no options: the entire data is the result */
+		kdebug("no options");
+		result_len = datalen;
+	} else {
+		const char *next_opt;
+
+		result_len = opt - data;
+		opt++;
+		kdebug("options: '%s'", opt);
+		do {
+			const char *eq;
+			int opt_len, opt_nlen, opt_vlen, tmp;
+
+			next_opt = memchr(opt, '#', end - opt) ?: end;
+			opt_len = next_opt - opt;
+			if (!opt_len) {
+				printk(KERN_WARNING
+				       "Empty option to dns_resolver key %d\n",
+				       key->serial);
+				return -EINVAL;
+			}
+
+			eq = memchr(opt, '=', opt_len) ?: end;
+			opt_nlen = eq - opt;
+			eq++;
+			opt_vlen = next_opt - eq; /* will be -1 if no value */
+
+			tmp = opt_vlen >= 0 ? opt_vlen : 0;
+			kdebug("option '%*.*s' val '%*.*s'",
+			       opt_nlen, opt_nlen, opt, tmp, tmp, eq);
+
+			/* see if it's a DNS error number */
+			if (opt_nlen == sizeof(DNS_ERRORNO_OPTION) - 1 &&
+			    memcmp(opt, DNS_ERRORNO_OPTION, opt_nlen) == 0) {
+				kdebug("dns error number option");
+				if (opt_vlen <= 0)
+					goto bad_option_value;
+
+				ret = strict_strtoul(eq, 10, &derrno);
+				if (ret < 0)
+					goto bad_option_value;
+
+				if (derrno > ERRNO_MAP_MAX)
+					goto bad_option_value;
+
+				kdebug("dns error no. = %lu", derrno);
+				key->type_data.x[0] = dns_errno_map[derrno];
+				continue;
+			}
+
+		bad_option_value:
+			printk(KERN_WARNING
+			       "Option '%*.*s' to dns_resolver key %d:"
+			       " bad/missing value\n",
+			       opt_nlen, opt_nlen, opt, key->serial);
+			return -EINVAL;
+		} while (opt = next_opt + 1, opt < end);
+	}
+
+	/* don't cache the result if we're caching an error saying there's no
+	 * result */
+	if (key->type_data.x[0]) {
+		kleave(" = 0 [h_error %ld]", key->type_data.x[0]);
+		return 0;
 	}
 
-	result_len = datalen;
+	kdebug("store result");
 	ret = key_payload_reserve(key, result_len);
 	if (ret < 0)
 		return -EINVAL;
diff --git a/net/dns_resolver/dns_query.c b/net/dns_resolver/dns_query.c
index 6c0cf31..ea4b120 100644
--- a/net/dns_resolver/dns_query.c
+++ b/net/dns_resolver/dns_query.c
@@ -135,6 +135,11 @@ int dns_query(const char *type, const char *name, size_t namelen,
 	if (ret < 0)
 		goto put;
 
+	/* If the DNS server gave an error, return that to the caller */
+	ret = rkey->type_data.x[0];
+	if (ret)
+		goto put;
+
 	upayload = rcu_dereference_protected(rkey->payload.data,
 					     lockdep_is_held(&rkey->sem));
 	len = upayload->datalen;

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH] DNS: If the DNS server returns an error, allow that to be cached
@ 2010-08-06 18:20 ` David Howells
  0 siblings, 0 replies; 6+ messages in thread
From: David Howells @ 2010-08-06 18:20 UTC (permalink / raw)
  To: smfrench, jlayton
  Cc: linux-fsdevel, linux-cifs, linux-afs, linux-kernel, Wang Lei,
	David Howells

From: Wang Lei <wang840925@gmail.com>

If the DNS server returns an error, allow that to be cached in the DNS resolver
key in lieu of a value.  Userspace passes the h_errno from the libresolv
routines as an option in the payload:

	"#dnserror=<number>"

This is mapped through dns_errno_map[] to an appropriate Linux error code.  To
this end, an internal error EHOSTNOTFOUND is added to be passed back to the
caller to indicate the resultless lookup.

If this option is used, the main part of the payload is discarded.  The key may
still have an expiry time set, however.

Signed-off-by: Wang Lei <wang840925@gmail.com>
Signed-off-by: David Howells <dhowells@redhat.com>
---

 fs/afs/cell.c                |    5 +++
 include/linux/errno.h        |    3 ++
 net/dns_resolver/dns_key.c   |   84 ++++++++++++++++++++++++++++++++++++++++--
 net/dns_resolver/dns_query.c |    5 +++
 4 files changed, 93 insertions(+), 4 deletions(-)

diff --git a/fs/afs/cell.c b/fs/afs/cell.c
index ffea35c..acbefec 100644
--- a/fs/afs/cell.c
+++ b/fs/afs/cell.c
@@ -73,6 +73,11 @@ static struct afs_cell *afs_cell_alloc(const char *name, char *vllist)
 	if (!vllist || strlen(vllist) < 7) {
 		ret = dns_query("afsdb", name, namelen, "ipv4", &dvllist, NULL);
 		if (ret < 0) {
+			if (ret == -EHOSTNOTFOUND || ret == -EAGAIN ||
+			    ret == -ENOKEY)
+				/* translate these errors into something
+				 * userspace might understand */
+				ret = -EDESTADDRREQ;
 			_leave(" = %d", ret);
 			return ERR_PTR(ret);
 		}
diff --git a/include/linux/errno.h b/include/linux/errno.h
index 4668583..6c14a57 100644
--- a/include/linux/errno.h
+++ b/include/linux/errno.h
@@ -29,6 +29,9 @@
 #define EIOCBQUEUED	529	/* iocb queued, will get completion event */
 #define EIOCBRETRY	530	/* iocb queued, will trigger a retry */
 
+/* Defined for the DNS */
+#define EHOSTNOTFOUND	531	/* Server has no result for the request */
+
 #endif
 
 #endif
diff --git a/net/dns_resolver/dns_key.c b/net/dns_resolver/dns_key.c
index 1b1b411..c429d43 100644
--- a/net/dns_resolver/dns_key.c
+++ b/net/dns_resolver/dns_key.c
@@ -42,6 +42,18 @@ MODULE_PARM_DESC(debug, "DNS Resolver debugging mask");
 
 const struct cred *dns_resolver_cache;
 
+#define	DNS_ERRORNO_OPTION	"dnserror"
+
+static const int dns_errno_map[] = {
+	0,			/* no error */
+	-EHOSTNOTFOUND,		/* handle h_errno HOST_NOT_FOUND = 1 */
+	-EAGAIN,		/* handle h_errno TRY_AGAIN = 2 */
+	-ECONNREFUSED,		/* handle h_errno NO_RECOVERY = 3 */
+	-EHOSTNOTFOUND,		/* handle h_errno NO_DATA = 4 */
+};
+
+#define ERRNO_MAP_MAX  (ARRAY_SIZE(dns_errno_map) - 1)
+
 /*
  * Instantiate a user defined key for dns_resolver.
  *
@@ -58,9 +70,10 @@ static int
 dns_resolver_instantiate(struct key *key, const void *_data, size_t datalen)
 {
 	struct user_key_payload *upayload;
+	unsigned long derrno;
 	int ret;
 	size_t result_len = 0;
-	const char *data = _data, *opt;
+	const char *data = _data, *end, *opt;
 
 	kenter("%%%d,%s,'%s',%zu",
 	       key->serial, key->description, data, datalen);
@@ -70,13 +83,76 @@ dns_resolver_instantiate(struct key *key, const void *_data, size_t datalen)
 	datalen--;
 
 	/* deal with any options embedded in the data */
+	end = data + datalen;
 	opt = memchr(data, '#', datalen);
 	if (!opt) {
-		kdebug("no options currently supported");
-		return -EINVAL;
+		/* no options: the entire data is the result */
+		kdebug("no options");
+		result_len = datalen;
+	} else {
+		const char *next_opt;
+
+		result_len = opt - data;
+		opt++;
+		kdebug("options: '%s'", opt);
+		do {
+			const char *eq;
+			int opt_len, opt_nlen, opt_vlen, tmp;
+
+			next_opt = memchr(opt, '#', end - opt) ?: end;
+			opt_len = next_opt - opt;
+			if (!opt_len) {
+				printk(KERN_WARNING
+				       "Empty option to dns_resolver key %d\n",
+				       key->serial);
+				return -EINVAL;
+			}
+
+			eq = memchr(opt, '=', opt_len) ?: end;
+			opt_nlen = eq - opt;
+			eq++;
+			opt_vlen = next_opt - eq; /* will be -1 if no value */
+
+			tmp = opt_vlen >= 0 ? opt_vlen : 0;
+			kdebug("option '%*.*s' val '%*.*s'",
+			       opt_nlen, opt_nlen, opt, tmp, tmp, eq);
+
+			/* see if it's a DNS error number */
+			if (opt_nlen == sizeof(DNS_ERRORNO_OPTION) - 1 &&
+			    memcmp(opt, DNS_ERRORNO_OPTION, opt_nlen) == 0) {
+				kdebug("dns error number option");
+				if (opt_vlen <= 0)
+					goto bad_option_value;
+
+				ret = strict_strtoul(eq, 10, &derrno);
+				if (ret < 0)
+					goto bad_option_value;
+
+				if (derrno > ERRNO_MAP_MAX)
+					goto bad_option_value;
+
+				kdebug("dns error no. = %lu", derrno);
+				key->type_data.x[0] = dns_errno_map[derrno];
+				continue;
+			}
+
+		bad_option_value:
+			printk(KERN_WARNING
+			       "Option '%*.*s' to dns_resolver key %d:"
+			       " bad/missing value\n",
+			       opt_nlen, opt_nlen, opt, key->serial);
+			return -EINVAL;
+		} while (opt = next_opt + 1, opt < end);
+	}
+
+	/* don't cache the result if we're caching an error saying there's no
+	 * result */
+	if (key->type_data.x[0]) {
+		kleave(" = 0 [h_error %ld]", key->type_data.x[0]);
+		return 0;
 	}
 
-	result_len = datalen;
+	kdebug("store result");
 	ret = key_payload_reserve(key, result_len);
 	if (ret < 0)
 		return -EINVAL;
diff --git a/net/dns_resolver/dns_query.c b/net/dns_resolver/dns_query.c
index 6c0cf31..ea4b120 100644
--- a/net/dns_resolver/dns_query.c
+++ b/net/dns_resolver/dns_query.c
@@ -135,6 +135,11 @@ int dns_query(const char *type, const char *name, size_t namelen,
 	if (ret < 0)
 		goto put;
 
+	/* If the DNS server gave an error, return that to the caller */
+	ret = rkey->type_data.x[0];
+	if (ret)
+		goto put;
+
 	upayload = rcu_dereference_protected(rkey->payload.data,
 					     lockdep_is_held(&rkey->sem));
 	len = upayload->datalen;


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH] DNS: If the DNS server returns an error, allow that to be cached
  2010-08-06 18:20 ` David Howells
@ 2010-08-06 18:22     ` David Howells
  -1 siblings, 0 replies; 6+ messages in thread
From: David Howells @ 2010-08-06 18:22 UTC (permalink / raw)
  Cc: dhowells-H+wXaHxf7aLQT0dZR+AlfA, smfrench-Re5JQEeQqe8AvxtiuMwx3w,
	jlayton-H+wXaHxf7aLQT0dZR+AlfA,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-afs-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Wang Lei


The userspace upcall program is the attached.

David
---
/*
 * DNS Resolver Module User-space Helper for AFSDB records
 *
 * Copyright (C) Wang Lei (wang840925-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org) 2010
 * Authors: Wang Lei (wang840925-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org)
 *          David Howells (dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org)
 *
 * This is a userspace tool for querying AFSDB RR records in the DNS on behalf
 * of the kernel, and converting the VL server addresses to IPv4 format so that
 * they can be used by the kAFS filesystem.
 *
 * Compile with:
 *
 * 	cc -o dns.afsdb dns_query_afsdb.c -lresolv -lkeyutils
 *
 * As some function like res_init() should use the static liberary, which is a
 * bug of libresolv, that is the reason for cifs.upcall to reimplement.
 *
 * To use this program, you must tell /sbin/request-key how to invoke it.  You
 * need to have the keyutils package installed and something like the following
 * lines added to your /etc/request-key.conf file:
 *
 * 	#OP    TYPE         DESCRIPTION CALLOUT INFO PROGRAM ARG1 ARG2 ARG3 ...
 * 	====== ============ =========== ============ ==========================
 * 	create dns_resolver afsdb:*     *            /usr/sbin/dns.afsdb %k
 *
 *
 * 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
 */
#define _GNU_SOURCE
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <arpa/inet.h>
#include <resolv.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <syslog.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <keyutils.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>

static const char *DNS_PARSE_VERSION = "1.0";
static const char prog[] = "dns.upcall";
static const char key_type[] = "dns_resolver";
static const char afsdb_query_type[] = "afsdb";
static key_serial_t key = 0;
static int verbose = 0;


#define	MAX_VLS			15	/* Max Volume Location Servers Per-Cell */
#define DNS_EXPIRY_PREFIX	"expiry_time="
#define DNS_EXPIRY_TIME_LEN	10 /* 2^32 - 1 = 4294967295 */
#define AFSDB_MAX_DATA_LEN						\
	((MAX_VLS * (INET6_ADDRSTRLEN + 1)) + sizeof(DNS_EXPIRY_PREFIX) + \
	 DNS_EXPIRY_TIME_LEN + 1 /* '#'*/ + 1 /* end 0 */)

#define	INET_IP4_ONLY	0x1
#define	INET_IP6_ONLY	0x2
#define	INET_ALL	0xFF

#define DNS_ERR_PREFIX	"#dnserror="

/*
 * Print an error to stderr or the syslog, negate the key being created and
 * exit
 */
static __attribute__((format(printf, 1, 2), noreturn))
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.
	 */
	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
 */
static __attribute__((format(printf, 1, 2)))
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 status information
 */
static __attribute__((format(printf, 1, 2)))
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 __attribute__((noreturn))
void nsError(int err, const char *domain)
{
	char buf[AFSDB_MAX_DATA_LEN];
	int ret;

	if (isatty(2))
		fprintf(stderr, "%s: %s.\n", domain, hstrerror(err));
	else
		syslog(LOG_INFO, "%s: %s", domain, hstrerror(err));

	sprintf(buf, "%s%d", DNS_ERR_PREFIX, err);

	info("The key instantiation ERROR data is '%s'", buf);

	ret = keyctl_instantiate(key, buf, strlen(buf) + 1, 0);
	if (ret == -1)
		error("%s: keyctl_instantiate: %m", __func__);

	exit(0);
}

/*
 * Print debugging information
 */
static __attribute__((format(printf, 1, 2)))
void debug(const char *fmt, ...)
{
	va_list va;

	if (verbose < 2)
		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);
}

/*
 * Perform address resolution on a hostname
 */
static int
dns_resolver(char *server_name, char *ip, short mask)
{
	struct addrinfo hints, *addr;
	int ret, len;
	void *p;

	memset(&hints, 0, sizeof(hints));
	switch (mask) {
	case INET_IP4_ONLY:	hints.ai_family = AF_INET;	break;
	case INET_IP6_ONLY:	hints.ai_family = AF_INET6;	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;
	}

	/* convert ip to string form */
	if (addr->ai_family == AF_INET && (mask & INET_IP4_ONLY)) {
		p = &(((struct sockaddr_in *)addr->ai_addr)->sin_addr);
		len = INET_ADDRSTRLEN;
	} else if (addr->ai_family == AF_INET6 && (mask & INET_IP6_ONLY)) {
		p = &(((struct sockaddr_in6 *)addr->ai_addr)->sin6_addr);
		len = INET6_ADDRSTRLEN;
	} else {
		freeaddrinfo(addr);
		return -1;
	}

	if (!inet_ntop(addr->ai_family, p, ip, len))
		error("%s: inet_ntop: %m", __func__);

	freeaddrinfo(addr);
	return 0;
}

/*
 *
 */
static void
addVLServers(char *VLlist[],
	     int *vlsnum,
             ns_msg handle,
             ns_sect section,
	     char *result,
	     short mask,
	     unsigned long *_ttl)
{
	int rrnum;  /* resource record number */
	ns_rr rr;   /* expanded resource record */
	char ip[INET6_ADDRSTRLEN];
	char *p = result;
	int subtype, i, ret, alen;
	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),/* Start of the message	*/
				    ns_msg_end(handle), /* End of the message	*/
				    ns_rr_rdata(rr) + 2,    /* Position in the message*/
				    VLlist[*vlsnum],		/* Result	*/
				    MAXDNAME		/* Size of VLlist buffer*/
					       ) < 0)	/* Negative: error	*/
				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 name 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], ip, mask);
			if (ret) {
				debug("AFSDB RR can't resolve."
				      "subtype:%d, server name:%s, netmask:%d",
				      subtype, VLlist[*vlsnum], mask);
				goto next_one;
			}

			info("AFSDB RR subtype:%d, server name:%s, ip:%s, ttl:%u",
			     subtype, VLlist[*vlsnum], ip, ttl);

			/* colons are used in IPv6 addresses, so we use commas
			 * to separate IP addresses
			 */
			if (p > result)
				*p++ = ',';
			alen = strlen(ip);
			memcpy(p, ip, alen);
			p += alen;
			p[0] = '\0';

			/* prepare for the next record */
			(*vlsnum)++;
			continue;

		next_one:
			free(VLlist[*vlsnum]);
		}
	}

	*_ttl = ttl;
	info("ttl: %u", ttl);
}

/*
 * Look up the AFSDB record to get the VL server addresses.
 *
 * The callout_info is parsed for request options.  For instance, "ipv4" to
 * request only IPv4 addresses and "ipv6" to request only IPv6 addresses.
 */
static __attribute__((noreturn))
int dns_get_vlserver(key_serial_t key, const char *cell, char *options)
{
	int	ret;
	char	*VLlist[MAX_VLS];	/* list of name servers	*/
	char	ip[AFSDB_MAX_DATA_LEN];
	int	vlsnum = 0;		/* number of name servers in list */
	short	mask = INET_ALL;
	int	responseLen, len;	/* buffer length */
	ns_msg	handle;			/* handle for response message */
	unsigned long ttl = ULONG_MAX;
	union {
		HEADER hdr;
		u_char buf[NS_PACKETSZ];
	} response;		/* response buffers */

	debug("Get AFSDB RR for cell name:'%s', options:'%s'", cell, options);

	/* query the dns for an AFSDB RR */
	responseLen = res_query(cell,		/* the query to make */
				ns_c_in,	/* record class */
				ns_t_afsdb,	/* record type */
				response.buf,
				sizeof(response));
	if (responseLen < 0) {
		/* negative result; set an arbitrary timeout on the cache of 1
		 * minute */
		ret = keyctl_set_timeout(key, 1 * 60);
		if (ret == -1)
			error("%s: keyctl_set_timeout: %m", __func__);
		nsError(h_errno, cell);
	}

	if (ns_initparse(response.buf, responseLen, &handle) < 0)
		error("ns_initparse: %m");

	/* 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;

	/* look up a list of VL servers */
	addVLServers(VLlist, &vlsnum, handle, ns_s_an, ip, mask, &ttl);

	info("DNS query AFSDB RR results:'%s' ttl:%lu", ip, ttl);

	len = strlen(ip);

	/* set the key's expiry time from the minimum TTL encountered */
	ret = keyctl_set_timeout(key, ttl);
	if (ret == -1)
		error("%s: keyctl_set_timeout: %m", __func__);

	/* handle a lack of results */
	if (len == 0)
		nsError(NO_DATA, cell);

	info("The key instantiation data is '%s'", ip);

	/* load the key with data key */
	ret = keyctl_instantiate(key, ip, strlen(ip) + 1, 0);
	if (ret == -1)
		error("%s: keyctl_instantiate: %m", __func__);

	exit(0);
}

/*
 * Print usage details,
 */
static __attribute__((noreturn))
void usage(void)
{
	if (isatty(2))
		fprintf(stderr,
			"Usage: %s [-vv] [-C <callout_info>] [-D <desc>]"
			" key_serial\n",
			prog);
	else
		info("Usage: %s [-vv] key_serial", prog);
	keyctl_negate(key, 1, KEY_REQKEY_DEFL_DEFAULT);
	exit(2);
}

const struct option long_options[] = {
	{ "call-out",	1, NULL, 'C' },
	{ "desc",	1, NULL, 'D' },
	{ "verbose",	0, NULL, 'v' },
	{ "version",	0, NULL, 'V' },
	{ NULL,		0, NULL, 0 }
};


int main(int argc, char *argv[])
{
	int ktlen, qtlen, ret;
	char *keyend, *p;
	char *callout_info = NULL;
	char *buf = NULL, *name;
	char hostbuf[NI_MAXHOST];

	hostbuf[0] = '\0';

	openlog(prog, 0, LOG_DAEMON);

	while ((ret = getopt_long(argc, argv, "vD:C:", long_options, NULL)) != -1) {
		switch (ret) {
		case 'C':
			callout_info = optarg;
			continue;
		case 'D':
			ret = asprintf(&buf, "%s;-1;-1;0;%s", key_type, optarg);
			if (ret < 0)
				error("Error %m");
			continue;
		case 'V':
			printf("version: %s\n", DNS_PARSE_VERSION);
			exit(0);
		case 'v':
			verbose++;
			continue;
		default:
			if (!isatty(2))
				syslog(LOG_ERR, "unknown option: %c", ret);
			usage();
		}
	}

	argc -= optind;
	if (argc != 1)
		usage();
	argv += optind;

	/* get the key ID */
	errno = 0;
	key = strtol(*argv, NULL, 10);
	if (errno != 0)
		error("Invalid key ID format: %m");

	/* get the key description (of the form "x;x;x;x;<query_type>:<name>") */
	if (!buf) {
		ret = keyctl_describe_alloc(key, &buf);
		if (ret == -1)
			error("keyctl_describe_alloc failed: %m");
	}

	/* get the callout_info (which can supply options) */
	if (!callout_info) {
		ret = keyctl_read_alloc(KEY_SPEC_REQKEY_AUTH_KEY,
					(void **)&callout_info);
		if (ret == -1)
			error("Invalid key callout_info read: %m");
	}

	ret = 1;
	info("Key description: '%s'", buf);
	info("Callout info: '%s'", callout_info);

	p = strchr(buf, ';');
	if (!p)
		error("Badly formatted key description '%s'", buf);
	ktlen = p - buf;

	if (ktlen != sizeof(key_type) - 1 ||
	    memcmp(buf, key_type, ktlen) != 0)
		error("Key type is not supported: '%*.*s'", ktlen, ktlen, buf);

	keyend = buf + ktlen + 1;

	/* the actual key description is after the last semicolon */
	keyend = rindex(keyend, ';');
	if (!keyend)
		error("Invalid key description: %s", buf);
	keyend++;

	name = index(keyend, ':');
	if (!name)
		error("Missing query type: '%s'", keyend);
	qtlen = name - keyend;
	name++;

	if (qtlen == sizeof(afsdb_query_type) - 1 &&
	    memcmp(keyend, afsdb_query_type, sizeof(afsdb_query_type) - 1) == 0
	    ) {
		info("Do DNS query of AFSDB type for:'%s' mask:'%s'",
		     name, callout_info);
		dns_get_vlserver(key, name, callout_info);
	}

	error("Query type:\"%*.*s\" is not supported", qtlen, qtlen, keyend);
}

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] DNS: If the DNS server returns an error, allow that to be cached
@ 2010-08-06 18:22     ` David Howells
  0 siblings, 0 replies; 6+ messages in thread
From: David Howells @ 2010-08-06 18:22 UTC (permalink / raw)
  Cc: dhowells, smfrench, jlayton, linux-fsdevel, linux-cifs, linux-afs,
	linux-kernel, Wang Lei


The userspace upcall program is the attached.

David
---
/*
 * DNS Resolver Module User-space Helper for AFSDB records
 *
 * Copyright (C) Wang Lei (wang840925@gmail.com) 2010
 * Authors: Wang Lei (wang840925@gmail.com)
 *          David Howells (dhowells@redhat.com)
 *
 * This is a userspace tool for querying AFSDB RR records in the DNS on behalf
 * of the kernel, and converting the VL server addresses to IPv4 format so that
 * they can be used by the kAFS filesystem.
 *
 * Compile with:
 *
 * 	cc -o dns.afsdb dns_query_afsdb.c -lresolv -lkeyutils
 *
 * As some function like res_init() should use the static liberary, which is a
 * bug of libresolv, that is the reason for cifs.upcall to reimplement.
 *
 * To use this program, you must tell /sbin/request-key how to invoke it.  You
 * need to have the keyutils package installed and something like the following
 * lines added to your /etc/request-key.conf file:
 *
 * 	#OP    TYPE         DESCRIPTION CALLOUT INFO PROGRAM ARG1 ARG2 ARG3 ...
 * 	====== ============ =========== ============ ==========================
 * 	create dns_resolver afsdb:*     *            /usr/sbin/dns.afsdb %k
 *
 *
 * 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
 */
#define _GNU_SOURCE
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <arpa/inet.h>
#include <resolv.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <syslog.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <keyutils.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>

static const char *DNS_PARSE_VERSION = "1.0";
static const char prog[] = "dns.upcall";
static const char key_type[] = "dns_resolver";
static const char afsdb_query_type[] = "afsdb";
static key_serial_t key = 0;
static int verbose = 0;


#define	MAX_VLS			15	/* Max Volume Location Servers Per-Cell */
#define DNS_EXPIRY_PREFIX	"expiry_time="
#define DNS_EXPIRY_TIME_LEN	10 /* 2^32 - 1 = 4294967295 */
#define AFSDB_MAX_DATA_LEN						\
	((MAX_VLS * (INET6_ADDRSTRLEN + 1)) + sizeof(DNS_EXPIRY_PREFIX) + \
	 DNS_EXPIRY_TIME_LEN + 1 /* '#'*/ + 1 /* end 0 */)

#define	INET_IP4_ONLY	0x1
#define	INET_IP6_ONLY	0x2
#define	INET_ALL	0xFF

#define DNS_ERR_PREFIX	"#dnserror="

/*
 * Print an error to stderr or the syslog, negate the key being created and
 * exit
 */
static __attribute__((format(printf, 1, 2), noreturn))
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.
	 */
	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
 */
static __attribute__((format(printf, 1, 2)))
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 status information
 */
static __attribute__((format(printf, 1, 2)))
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 __attribute__((noreturn))
void nsError(int err, const char *domain)
{
	char buf[AFSDB_MAX_DATA_LEN];
	int ret;

	if (isatty(2))
		fprintf(stderr, "%s: %s.\n", domain, hstrerror(err));
	else
		syslog(LOG_INFO, "%s: %s", domain, hstrerror(err));

	sprintf(buf, "%s%d", DNS_ERR_PREFIX, err);

	info("The key instantiation ERROR data is '%s'", buf);

	ret = keyctl_instantiate(key, buf, strlen(buf) + 1, 0);
	if (ret == -1)
		error("%s: keyctl_instantiate: %m", __func__);

	exit(0);
}

/*
 * Print debugging information
 */
static __attribute__((format(printf, 1, 2)))
void debug(const char *fmt, ...)
{
	va_list va;

	if (verbose < 2)
		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);
}

/*
 * Perform address resolution on a hostname
 */
static int
dns_resolver(char *server_name, char *ip, short mask)
{
	struct addrinfo hints, *addr;
	int ret, len;
	void *p;

	memset(&hints, 0, sizeof(hints));
	switch (mask) {
	case INET_IP4_ONLY:	hints.ai_family = AF_INET;	break;
	case INET_IP6_ONLY:	hints.ai_family = AF_INET6;	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;
	}

	/* convert ip to string form */
	if (addr->ai_family == AF_INET && (mask & INET_IP4_ONLY)) {
		p = &(((struct sockaddr_in *)addr->ai_addr)->sin_addr);
		len = INET_ADDRSTRLEN;
	} else if (addr->ai_family == AF_INET6 && (mask & INET_IP6_ONLY)) {
		p = &(((struct sockaddr_in6 *)addr->ai_addr)->sin6_addr);
		len = INET6_ADDRSTRLEN;
	} else {
		freeaddrinfo(addr);
		return -1;
	}

	if (!inet_ntop(addr->ai_family, p, ip, len))
		error("%s: inet_ntop: %m", __func__);

	freeaddrinfo(addr);
	return 0;
}

/*
 *
 */
static void
addVLServers(char *VLlist[],
	     int *vlsnum,
             ns_msg handle,
             ns_sect section,
	     char *result,
	     short mask,
	     unsigned long *_ttl)
{
	int rrnum;  /* resource record number */
	ns_rr rr;   /* expanded resource record */
	char ip[INET6_ADDRSTRLEN];
	char *p = result;
	int subtype, i, ret, alen;
	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),/* Start of the message	*/
				    ns_msg_end(handle), /* End of the message	*/
				    ns_rr_rdata(rr) + 2,    /* Position in the message*/
				    VLlist[*vlsnum],		/* Result	*/
				    MAXDNAME		/* Size of VLlist buffer*/
					       ) < 0)	/* Negative: error	*/
				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 name 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], ip, mask);
			if (ret) {
				debug("AFSDB RR can't resolve."
				      "subtype:%d, server name:%s, netmask:%d",
				      subtype, VLlist[*vlsnum], mask);
				goto next_one;
			}

			info("AFSDB RR subtype:%d, server name:%s, ip:%s, ttl:%u",
			     subtype, VLlist[*vlsnum], ip, ttl);

			/* colons are used in IPv6 addresses, so we use commas
			 * to separate IP addresses
			 */
			if (p > result)
				*p++ = ',';
			alen = strlen(ip);
			memcpy(p, ip, alen);
			p += alen;
			p[0] = '\0';

			/* prepare for the next record */
			(*vlsnum)++;
			continue;

		next_one:
			free(VLlist[*vlsnum]);
		}
	}

	*_ttl = ttl;
	info("ttl: %u", ttl);
}

/*
 * Look up the AFSDB record to get the VL server addresses.
 *
 * The callout_info is parsed for request options.  For instance, "ipv4" to
 * request only IPv4 addresses and "ipv6" to request only IPv6 addresses.
 */
static __attribute__((noreturn))
int dns_get_vlserver(key_serial_t key, const char *cell, char *options)
{
	int	ret;
	char	*VLlist[MAX_VLS];	/* list of name servers	*/
	char	ip[AFSDB_MAX_DATA_LEN];
	int	vlsnum = 0;		/* number of name servers in list */
	short	mask = INET_ALL;
	int	responseLen, len;	/* buffer length */
	ns_msg	handle;			/* handle for response message */
	unsigned long ttl = ULONG_MAX;
	union {
		HEADER hdr;
		u_char buf[NS_PACKETSZ];
	} response;		/* response buffers */

	debug("Get AFSDB RR for cell name:'%s', options:'%s'", cell, options);

	/* query the dns for an AFSDB RR */
	responseLen = res_query(cell,		/* the query to make */
				ns_c_in,	/* record class */
				ns_t_afsdb,	/* record type */
				response.buf,
				sizeof(response));
	if (responseLen < 0) {
		/* negative result; set an arbitrary timeout on the cache of 1
		 * minute */
		ret = keyctl_set_timeout(key, 1 * 60);
		if (ret == -1)
			error("%s: keyctl_set_timeout: %m", __func__);
		nsError(h_errno, cell);
	}

	if (ns_initparse(response.buf, responseLen, &handle) < 0)
		error("ns_initparse: %m");

	/* 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;

	/* look up a list of VL servers */
	addVLServers(VLlist, &vlsnum, handle, ns_s_an, ip, mask, &ttl);

	info("DNS query AFSDB RR results:'%s' ttl:%lu", ip, ttl);

	len = strlen(ip);

	/* set the key's expiry time from the minimum TTL encountered */
	ret = keyctl_set_timeout(key, ttl);
	if (ret == -1)
		error("%s: keyctl_set_timeout: %m", __func__);

	/* handle a lack of results */
	if (len == 0)
		nsError(NO_DATA, cell);

	info("The key instantiation data is '%s'", ip);

	/* load the key with data key */
	ret = keyctl_instantiate(key, ip, strlen(ip) + 1, 0);
	if (ret == -1)
		error("%s: keyctl_instantiate: %m", __func__);

	exit(0);
}

/*
 * Print usage details,
 */
static __attribute__((noreturn))
void usage(void)
{
	if (isatty(2))
		fprintf(stderr,
			"Usage: %s [-vv] [-C <callout_info>] [-D <desc>]"
			" key_serial\n",
			prog);
	else
		info("Usage: %s [-vv] key_serial", prog);
	keyctl_negate(key, 1, KEY_REQKEY_DEFL_DEFAULT);
	exit(2);
}

const struct option long_options[] = {
	{ "call-out",	1, NULL, 'C' },
	{ "desc",	1, NULL, 'D' },
	{ "verbose",	0, NULL, 'v' },
	{ "version",	0, NULL, 'V' },
	{ NULL,		0, NULL, 0 }
};


int main(int argc, char *argv[])
{
	int ktlen, qtlen, ret;
	char *keyend, *p;
	char *callout_info = NULL;
	char *buf = NULL, *name;
	char hostbuf[NI_MAXHOST];

	hostbuf[0] = '\0';

	openlog(prog, 0, LOG_DAEMON);

	while ((ret = getopt_long(argc, argv, "vD:C:", long_options, NULL)) != -1) {
		switch (ret) {
		case 'C':
			callout_info = optarg;
			continue;
		case 'D':
			ret = asprintf(&buf, "%s;-1;-1;0;%s", key_type, optarg);
			if (ret < 0)
				error("Error %m");
			continue;
		case 'V':
			printf("version: %s\n", DNS_PARSE_VERSION);
			exit(0);
		case 'v':
			verbose++;
			continue;
		default:
			if (!isatty(2))
				syslog(LOG_ERR, "unknown option: %c", ret);
			usage();
		}
	}

	argc -= optind;
	if (argc != 1)
		usage();
	argv += optind;

	/* get the key ID */
	errno = 0;
	key = strtol(*argv, NULL, 10);
	if (errno != 0)
		error("Invalid key ID format: %m");

	/* get the key description (of the form "x;x;x;x;<query_type>:<name>") */
	if (!buf) {
		ret = keyctl_describe_alloc(key, &buf);
		if (ret == -1)
			error("keyctl_describe_alloc failed: %m");
	}

	/* get the callout_info (which can supply options) */
	if (!callout_info) {
		ret = keyctl_read_alloc(KEY_SPEC_REQKEY_AUTH_KEY,
					(void **)&callout_info);
		if (ret == -1)
			error("Invalid key callout_info read: %m");
	}

	ret = 1;
	info("Key description: '%s'", buf);
	info("Callout info: '%s'", callout_info);

	p = strchr(buf, ';');
	if (!p)
		error("Badly formatted key description '%s'", buf);
	ktlen = p - buf;

	if (ktlen != sizeof(key_type) - 1 ||
	    memcmp(buf, key_type, ktlen) != 0)
		error("Key type is not supported: '%*.*s'", ktlen, ktlen, buf);

	keyend = buf + ktlen + 1;

	/* the actual key description is after the last semicolon */
	keyend = rindex(keyend, ';');
	if (!keyend)
		error("Invalid key description: %s", buf);
	keyend++;

	name = index(keyend, ':');
	if (!name)
		error("Missing query type: '%s'", keyend);
	qtlen = name - keyend;
	name++;

	if (qtlen == sizeof(afsdb_query_type) - 1 &&
	    memcmp(keyend, afsdb_query_type, sizeof(afsdb_query_type) - 1) == 0
	    ) {
		info("Do DNS query of AFSDB type for:'%s' mask:'%s'",
		     name, callout_info);
		dns_get_vlserver(key, name, callout_info);
	}

	error("Query type:\"%*.*s\" is not supported", qtlen, qtlen, keyend);
}


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] DNS: If the DNS server returns an error, allow that to be cached
  2010-08-06 18:20 ` David Howells
@ 2010-08-06 18:27     ` Jeff Layton
  -1 siblings, 0 replies; 6+ messages in thread
From: Jeff Layton @ 2010-08-06 18:27 UTC (permalink / raw)
  To: David Howells
  Cc: smfrench-Re5JQEeQqe8AvxtiuMwx3w,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA,
	linux-afs-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Wang Lei

On Fri, 06 Aug 2010 19:20:39 +0100
David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org> wrote:

> From: Wang Lei <wang840925-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> 
> If the DNS server returns an error, allow that to be cached in the DNS resolver
> key in lieu of a value.  Userspace passes the h_errno from the libresolv
> routines as an option in the payload:
> 
> 	"#dnserror=<number>"
> 
> This is mapped through dns_errno_map[] to an appropriate Linux error code.  To
> this end, an internal error EHOSTNOTFOUND is added to be passed back to the
> caller to indicate the resultless lookup.
> 
> If this option is used, the main part of the payload is discarded.  The key may
> still have an expiry time set, however.
> 
> Signed-off-by: Wang Lei <wang840925-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> Signed-off-by: David Howells <dhowells-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> ---
> 
>  fs/afs/cell.c                |    5 +++
>  include/linux/errno.h        |    3 ++
>  net/dns_resolver/dns_key.c   |   84 ++++++++++++++++++++++++++++++++++++++++--
>  net/dns_resolver/dns_query.c |    5 +++
>  4 files changed, 93 insertions(+), 4 deletions(-)
> 
> diff --git a/fs/afs/cell.c b/fs/afs/cell.c
> index ffea35c..acbefec 100644
> --- a/fs/afs/cell.c
> +++ b/fs/afs/cell.c
> @@ -73,6 +73,11 @@ static struct afs_cell *afs_cell_alloc(const char *name, char *vllist)
>  	if (!vllist || strlen(vllist) < 7) {
>  		ret = dns_query("afsdb", name, namelen, "ipv4", &dvllist, NULL);
>  		if (ret < 0) {
> +			if (ret == -EHOSTNOTFOUND || ret == -EAGAIN ||
> +			    ret == -ENOKEY)
> +				/* translate these errors into something
> +				 * userspace might understand */
> +				ret = -EDESTADDRREQ;
>  			_leave(" = %d", ret);
>  			return ERR_PTR(ret);
>  		}
> diff --git a/include/linux/errno.h b/include/linux/errno.h
> index 4668583..6c14a57 100644
> --- a/include/linux/errno.h
> +++ b/include/linux/errno.h
> @@ -29,6 +29,9 @@
>  #define EIOCBQUEUED	529	/* iocb queued, will get completion event */
>  #define EIOCBRETRY	530	/* iocb queued, will trigger a retry */
>  
> +/* Defined for the DNS */
> +#define EHOSTNOTFOUND	531	/* Server has no result for the request */
> +
>  #endif
>  
>  #endif
> diff --git a/net/dns_resolver/dns_key.c b/net/dns_resolver/dns_key.c
> index 1b1b411..c429d43 100644
> --- a/net/dns_resolver/dns_key.c
> +++ b/net/dns_resolver/dns_key.c
> @@ -42,6 +42,18 @@ MODULE_PARM_DESC(debug, "DNS Resolver debugging mask");
>  
>  const struct cred *dns_resolver_cache;
>  
> +#define	DNS_ERRORNO_OPTION	"dnserror"
> +
> +static const int dns_errno_map[] = {
> +	0,			/* no error */
> +	-EHOSTNOTFOUND,		/* handle h_errno HOST_NOT_FOUND = 1 */
> +	-EAGAIN,		/* handle h_errno TRY_AGAIN = 2 */
> +	-ECONNREFUSED,		/* handle h_errno NO_RECOVERY = 3 */
> +	-EHOSTNOTFOUND,		/* handle h_errno NO_DATA = 4 */
> +};
> +
> +#define ERRNO_MAP_MAX  (ARRAY_SIZE(dns_errno_map) - 1)
> +
	^^^^^
I'm not thrilled with the error mapping here. It seems like something
that could be prone to breakage in the future. Would it be better to
just have the userspace prog send up the error and just have the kernel
check it for validity?

That does make dealing with EHOSTNOTFOUND a little more tricky though
as it's considered internal to the kernel...

Overall though, the patch seems reasonable and like something we'll
want to ensure that the kernel doesn't spam userspace with requests.

>  /*
>   * Instantiate a user defined key for dns_resolver.
>   *
> @@ -58,9 +70,10 @@ static int
>  dns_resolver_instantiate(struct key *key, const void *_data, size_t datalen)
>  {
>  	struct user_key_payload *upayload;
> +	unsigned long derrno;
>  	int ret;
>  	size_t result_len = 0;
> -	const char *data = _data, *opt;
> +	const char *data = _data, *end, *opt;
>  
>  	kenter("%%%d,%s,'%s',%zu",
>  	       key->serial, key->description, data, datalen);
> @@ -70,13 +83,76 @@ dns_resolver_instantiate(struct key *key, const void *_data, size_t datalen)
>  	datalen--;
>  
>  	/* deal with any options embedded in the data */
> +	end = data + datalen;
>  	opt = memchr(data, '#', datalen);
>  	if (!opt) {
> -		kdebug("no options currently supported");
> -		return -EINVAL;
> +		/* no options: the entire data is the result */
> +		kdebug("no options");
> +		result_len = datalen;
> +	} else {
> +		const char *next_opt;
> +
> +		result_len = opt - data;
> +		opt++;
> +		kdebug("options: '%s'", opt);
> +		do {
> +			const char *eq;
> +			int opt_len, opt_nlen, opt_vlen, tmp;
> +
> +			next_opt = memchr(opt, '#', end - opt) ?: end;
> +			opt_len = next_opt - opt;
> +			if (!opt_len) {
> +				printk(KERN_WARNING
> +				       "Empty option to dns_resolver key %d\n",
> +				       key->serial);
> +				return -EINVAL;
> +			}
> +
> +			eq = memchr(opt, '=', opt_len) ?: end;
> +			opt_nlen = eq - opt;
> +			eq++;
> +			opt_vlen = next_opt - eq; /* will be -1 if no value */
> +
> +			tmp = opt_vlen >= 0 ? opt_vlen : 0;
> +			kdebug("option '%*.*s' val '%*.*s'",
> +			       opt_nlen, opt_nlen, opt, tmp, tmp, eq);
> +
> +			/* see if it's a DNS error number */
> +			if (opt_nlen == sizeof(DNS_ERRORNO_OPTION) - 1 &&
> +			    memcmp(opt, DNS_ERRORNO_OPTION, opt_nlen) == 0) {
> +				kdebug("dns error number option");
> +				if (opt_vlen <= 0)
> +					goto bad_option_value;
> +
> +				ret = strict_strtoul(eq, 10, &derrno);
> +				if (ret < 0)
> +					goto bad_option_value;
> +
> +				if (derrno > ERRNO_MAP_MAX)
> +					goto bad_option_value;
> +
> +				kdebug("dns error no. = %lu", derrno);
> +				key->type_data.x[0] = dns_errno_map[derrno];
> +				continue;
> +			}
> +
> +		bad_option_value:
> +			printk(KERN_WARNING
> +			       "Option '%*.*s' to dns_resolver key %d:"
> +			       " bad/missing value\n",
> +			       opt_nlen, opt_nlen, opt, key->serial);
> +			return -EINVAL;
> +		} while (opt = next_opt + 1, opt < end);
> +	}
> +
> +	/* don't cache the result if we're caching an error saying there's no
> +	 * result */
> +	if (key->type_data.x[0]) {
> +		kleave(" = 0 [h_error %ld]", key->type_data.x[0]);
> +		return 0;
>  	}
>  
> -	result_len = datalen;
> +	kdebug("store result");
>  	ret = key_payload_reserve(key, result_len);
>  	if (ret < 0)
>  		return -EINVAL;
> diff --git a/net/dns_resolver/dns_query.c b/net/dns_resolver/dns_query.c
> index 6c0cf31..ea4b120 100644
> --- a/net/dns_resolver/dns_query.c
> +++ b/net/dns_resolver/dns_query.c
> @@ -135,6 +135,11 @@ int dns_query(const char *type, const char *name, size_t namelen,
>  	if (ret < 0)
>  		goto put;
>  
> +	/* If the DNS server gave an error, return that to the caller */
> +	ret = rkey->type_data.x[0];
> +	if (ret)
> +		goto put;
> +
>  	upayload = rcu_dereference_protected(rkey->payload.data,
>  					     lockdep_is_held(&rkey->sem));
>  	len = upayload->datalen;
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 


-- 
Jeff Layton <jlayton-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] DNS: If the DNS server returns an error, allow that to be cached
@ 2010-08-06 18:27     ` Jeff Layton
  0 siblings, 0 replies; 6+ messages in thread
From: Jeff Layton @ 2010-08-06 18:27 UTC (permalink / raw)
  To: David Howells
  Cc: smfrench, linux-fsdevel, linux-cifs, linux-afs, linux-kernel,
	Wang Lei

On Fri, 06 Aug 2010 19:20:39 +0100
David Howells <dhowells@redhat.com> wrote:

> From: Wang Lei <wang840925@gmail.com>
> 
> If the DNS server returns an error, allow that to be cached in the DNS resolver
> key in lieu of a value.  Userspace passes the h_errno from the libresolv
> routines as an option in the payload:
> 
> 	"#dnserror=<number>"
> 
> This is mapped through dns_errno_map[] to an appropriate Linux error code.  To
> this end, an internal error EHOSTNOTFOUND is added to be passed back to the
> caller to indicate the resultless lookup.
> 
> If this option is used, the main part of the payload is discarded.  The key may
> still have an expiry time set, however.
> 
> Signed-off-by: Wang Lei <wang840925@gmail.com>
> Signed-off-by: David Howells <dhowells@redhat.com>
> ---
> 
>  fs/afs/cell.c                |    5 +++
>  include/linux/errno.h        |    3 ++
>  net/dns_resolver/dns_key.c   |   84 ++++++++++++++++++++++++++++++++++++++++--
>  net/dns_resolver/dns_query.c |    5 +++
>  4 files changed, 93 insertions(+), 4 deletions(-)
> 
> diff --git a/fs/afs/cell.c b/fs/afs/cell.c
> index ffea35c..acbefec 100644
> --- a/fs/afs/cell.c
> +++ b/fs/afs/cell.c
> @@ -73,6 +73,11 @@ static struct afs_cell *afs_cell_alloc(const char *name, char *vllist)
>  	if (!vllist || strlen(vllist) < 7) {
>  		ret = dns_query("afsdb", name, namelen, "ipv4", &dvllist, NULL);
>  		if (ret < 0) {
> +			if (ret == -EHOSTNOTFOUND || ret == -EAGAIN ||
> +			    ret == -ENOKEY)
> +				/* translate these errors into something
> +				 * userspace might understand */
> +				ret = -EDESTADDRREQ;
>  			_leave(" = %d", ret);
>  			return ERR_PTR(ret);
>  		}
> diff --git a/include/linux/errno.h b/include/linux/errno.h
> index 4668583..6c14a57 100644
> --- a/include/linux/errno.h
> +++ b/include/linux/errno.h
> @@ -29,6 +29,9 @@
>  #define EIOCBQUEUED	529	/* iocb queued, will get completion event */
>  #define EIOCBRETRY	530	/* iocb queued, will trigger a retry */
>  
> +/* Defined for the DNS */
> +#define EHOSTNOTFOUND	531	/* Server has no result for the request */
> +
>  #endif
>  
>  #endif
> diff --git a/net/dns_resolver/dns_key.c b/net/dns_resolver/dns_key.c
> index 1b1b411..c429d43 100644
> --- a/net/dns_resolver/dns_key.c
> +++ b/net/dns_resolver/dns_key.c
> @@ -42,6 +42,18 @@ MODULE_PARM_DESC(debug, "DNS Resolver debugging mask");
>  
>  const struct cred *dns_resolver_cache;
>  
> +#define	DNS_ERRORNO_OPTION	"dnserror"
> +
> +static const int dns_errno_map[] = {
> +	0,			/* no error */
> +	-EHOSTNOTFOUND,		/* handle h_errno HOST_NOT_FOUND = 1 */
> +	-EAGAIN,		/* handle h_errno TRY_AGAIN = 2 */
> +	-ECONNREFUSED,		/* handle h_errno NO_RECOVERY = 3 */
> +	-EHOSTNOTFOUND,		/* handle h_errno NO_DATA = 4 */
> +};
> +
> +#define ERRNO_MAP_MAX  (ARRAY_SIZE(dns_errno_map) - 1)
> +
	^^^^^
I'm not thrilled with the error mapping here. It seems like something
that could be prone to breakage in the future. Would it be better to
just have the userspace prog send up the error and just have the kernel
check it for validity?

That does make dealing with EHOSTNOTFOUND a little more tricky though
as it's considered internal to the kernel...

Overall though, the patch seems reasonable and like something we'll
want to ensure that the kernel doesn't spam userspace with requests.

>  /*
>   * Instantiate a user defined key for dns_resolver.
>   *
> @@ -58,9 +70,10 @@ static int
>  dns_resolver_instantiate(struct key *key, const void *_data, size_t datalen)
>  {
>  	struct user_key_payload *upayload;
> +	unsigned long derrno;
>  	int ret;
>  	size_t result_len = 0;
> -	const char *data = _data, *opt;
> +	const char *data = _data, *end, *opt;
>  
>  	kenter("%%%d,%s,'%s',%zu",
>  	       key->serial, key->description, data, datalen);
> @@ -70,13 +83,76 @@ dns_resolver_instantiate(struct key *key, const void *_data, size_t datalen)
>  	datalen--;
>  
>  	/* deal with any options embedded in the data */
> +	end = data + datalen;
>  	opt = memchr(data, '#', datalen);
>  	if (!opt) {
> -		kdebug("no options currently supported");
> -		return -EINVAL;
> +		/* no options: the entire data is the result */
> +		kdebug("no options");
> +		result_len = datalen;
> +	} else {
> +		const char *next_opt;
> +
> +		result_len = opt - data;
> +		opt++;
> +		kdebug("options: '%s'", opt);
> +		do {
> +			const char *eq;
> +			int opt_len, opt_nlen, opt_vlen, tmp;
> +
> +			next_opt = memchr(opt, '#', end - opt) ?: end;
> +			opt_len = next_opt - opt;
> +			if (!opt_len) {
> +				printk(KERN_WARNING
> +				       "Empty option to dns_resolver key %d\n",
> +				       key->serial);
> +				return -EINVAL;
> +			}
> +
> +			eq = memchr(opt, '=', opt_len) ?: end;
> +			opt_nlen = eq - opt;
> +			eq++;
> +			opt_vlen = next_opt - eq; /* will be -1 if no value */
> +
> +			tmp = opt_vlen >= 0 ? opt_vlen : 0;
> +			kdebug("option '%*.*s' val '%*.*s'",
> +			       opt_nlen, opt_nlen, opt, tmp, tmp, eq);
> +
> +			/* see if it's a DNS error number */
> +			if (opt_nlen == sizeof(DNS_ERRORNO_OPTION) - 1 &&
> +			    memcmp(opt, DNS_ERRORNO_OPTION, opt_nlen) == 0) {
> +				kdebug("dns error number option");
> +				if (opt_vlen <= 0)
> +					goto bad_option_value;
> +
> +				ret = strict_strtoul(eq, 10, &derrno);
> +				if (ret < 0)
> +					goto bad_option_value;
> +
> +				if (derrno > ERRNO_MAP_MAX)
> +					goto bad_option_value;
> +
> +				kdebug("dns error no. = %lu", derrno);
> +				key->type_data.x[0] = dns_errno_map[derrno];
> +				continue;
> +			}
> +
> +		bad_option_value:
> +			printk(KERN_WARNING
> +			       "Option '%*.*s' to dns_resolver key %d:"
> +			       " bad/missing value\n",
> +			       opt_nlen, opt_nlen, opt, key->serial);
> +			return -EINVAL;
> +		} while (opt = next_opt + 1, opt < end);
> +	}
> +
> +	/* don't cache the result if we're caching an error saying there's no
> +	 * result */
> +	if (key->type_data.x[0]) {
> +		kleave(" = 0 [h_error %ld]", key->type_data.x[0]);
> +		return 0;
>  	}
>  
> -	result_len = datalen;
> +	kdebug("store result");
>  	ret = key_payload_reserve(key, result_len);
>  	if (ret < 0)
>  		return -EINVAL;
> diff --git a/net/dns_resolver/dns_query.c b/net/dns_resolver/dns_query.c
> index 6c0cf31..ea4b120 100644
> --- a/net/dns_resolver/dns_query.c
> +++ b/net/dns_resolver/dns_query.c
> @@ -135,6 +135,11 @@ int dns_query(const char *type, const char *name, size_t namelen,
>  	if (ret < 0)
>  		goto put;
>  
> +	/* If the DNS server gave an error, return that to the caller */
> +	ret = rkey->type_data.x[0];
> +	if (ret)
> +		goto put;
> +
>  	upayload = rcu_dereference_protected(rkey->payload.data,
>  					     lockdep_is_held(&rkey->sem));
>  	len = upayload->datalen;
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 


-- 
Jeff Layton <jlayton@redhat.com>

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2010-08-06 18:30 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-08-06 18:20 [PATCH] DNS: If the DNS server returns an error, allow that to be cached David Howells
2010-08-06 18:20 ` David Howells
     [not found] ` <20100806182039.5615.62116.stgit-S6HVgzuS8uM4Awkfq6JHfwNdhmdF6hFW@public.gmane.org>
2010-08-06 18:22   ` David Howells
2010-08-06 18:22     ` David Howells
2010-08-06 18:27   ` Jeff Layton
2010-08-06 18:27     ` Jeff Layton

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.