1729 lines · 39.0 KB · C
RawDownload
/*  * This file is part of the UCB release of Plan 9. It is subject to the license * terms in the LICENSE file found in the top-level directory of this * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No * part of the UCB release of Plan 9, including this file, may be copied, * modified, propagated, or distributed except according to the terms contained * in the LICENSE file. */ /* * domain name resolvers, see rfcs 1035 and 1123 */#include <u.h>#include <libc.h>#include <ip.h>#include <bio.h>#include <ndb.h>#include "dns.h" typedef struct Dest Dest;typedef struct Ipaddr Ipaddr;typedef struct Query Query; enum{	Udp, Tcp, 	Answerr=	-1,	Answnone, 	Maxdest=	24,	/* maximum destinations for a request message */	Maxoutstanding=	15,	/* max. outstanding queries per domain name */	Remntretry=	15,	/* min. sec.s between /net.alt remount tries */ 	/*	 * these are the old values; we're trying longer timeouts now	 * primarily for the benefit of remote nameservers querying us	 * during times of bad connectivity.	 *///	Maxtrans=	3,	/* maximum transmissions to a server *///	Maxretries=	3, /* cname+actual resends: was 32; have pity on user *///	Maxwaitms=	1000,	/* wait no longer for a remote dns query *///	Minwaitms=	100,	/* willing to wait for a remote dns query */ 	Maxtrans=	5,	/* maximum transmissions to a server */	Maxretries=	5, /* cname+actual resends: was 32; have pity on user */	Maxwaitms=	5000,	/* wait no longer for a remote dns query */	Minwaitms=	500,	/* willing to wait for a remote dns query */ 	Destmagic=	0xcafebabe,	Querymagic=	0xdeadbeef,};enum { Hurry, Patient, };enum { Outns, Inns, }; struct Ipaddr {	Ipaddr *next;	uchar	ip[IPaddrlen];}; struct Dest{	uchar	a[IPaddrlen];	/* ip address */	DN	*s;		/* name server */	int	nx;		/* number of transmissions */	int	code;		/* response code; used to clear dp->respcode */ 	ulong	magic;}; /* * Query has a QLock in it, thus it can't be an automatic * variable, since each process would see a separate copy * of the lock on its stack. */struct Query {	DN	*dp;		/* domain */	ushort	type;		/* and type to look up */	Request *req;	RR	*nsrp;		/* name servers to consult */ 	/* dest must not be on the stack due to forking in slave() */	Dest	*dest;		/* array of destinations */	Dest	*curdest;	/* pointer to next to fill */	int	ndest;		/* transmit to this many on this round */ 	int	udpfd; 	QLock	tcplock;	/* only one tcp call at a time per query */	int	tcpset;	int	tcpfd;		/* if Tcp, read replies from here */	int	tcpctlfd;	uchar	tcpip[IPaddrlen]; 	ulong	magic;}; /* estimated % probability of such a record existing at all */int likely[] = {	[Ta]		95,	[Taaaa]		10,	[Tcname]	15,	[Tmx]		60,	[Tns]		90,	[Tnull]		5,	[Tptr]		35,	[Tsoa]		90,	[Tsrv]		60,	[Ttxt]		15,	[Tall]		95,}; static RR*	dnresolve1(char*, int, int, Request*, int, int);static int	netquery(Query *, int); /* * reading /proc/pid/args yields either "name args" or "name [display args]", * so return only display args, if any. */static char *procgetname(void){	int fd, n;	char *lp, *rp;	char buf[256]; 	snprint(buf, sizeof buf, "#p/%d/args", getpid());	if((fd = open(buf, OREAD)) < 0)		return strdup("");	*buf = '\0';	n = read(fd, buf, sizeof buf-1);	close(fd);	if (n >= 0)		buf[n] = '\0';	if ((lp = strchr(buf, '[')) == nil ||	    (rp = strrchr(buf, ']')) == nil)		return strdup("");	*rp = '\0';	return strdup(lp+1);} voidrrfreelistptr(RR **rpp){	RR *rp; 	if (rpp == nil || *rpp == nil)		return;	rp = *rpp;	*rpp = nil;	/* update pointer in memory before freeing list */	rrfreelist(rp);} /* *  lookup 'type' info for domain name 'name'.  If it doesn't exist, try *  looking it up as a canonical name. * *  this process can be quite slow if time-outs are set too high when querying *  nameservers that just don't respond to certain query types.  in that case, *  there will be multiple udp retries, multiple nameservers will be queried, *  and this will be repeated for a cname query.  the whole thing will be *  retried several times until we get an answer or a time-out. */RR*dnresolve(char *name, int class, int type, Request *req, RR **cn, int depth,	int recurse, int rooted, int *status){	RR *rp, *nrp, *drp;	DN *dp;	int loops;	char *procname;	char nname[Domlen]; 	if(status)		*status = 0; 	if(depth > 12)			/* in a recursive loop? */		return nil; 	procname = procgetname();	/*	 *  hack for systems that don't have resolve search	 *  lists.  Just look up the simple name in the database.	 */	if(!rooted && strchr(name, '.') == nil){		rp = nil;		drp = domainlist(class);		for(nrp = drp; rp == nil && nrp != nil; nrp = nrp->next){			snprint(nname, sizeof nname, "%s.%s", name,				nrp->ptr->name);			rp = dnresolve(nname, class, type, req, cn, depth+1,				recurse, rooted, status);			lock(&dnlock);			rrfreelist(rrremneg(&rp));			unlock(&dnlock);		}		if(drp != nil)			rrfreelist(drp);		procsetname(procname);		free(procname);		return rp;	} 	/*	 *  try the name directly	 */	rp = dnresolve1(name, class, type, req, depth, recurse);	if(rp == nil) {		/*		 * try it as a canonical name if we weren't told		 * that the name didn't exist		 */		dp = dnlookup(name, class, 0);		if(type != Tptr && dp->respcode != Rname)			for(loops = 0; rp == nil && loops < Maxretries; loops++){				/* retry cname, then the actual type */				rp = dnresolve1(name, class, Tcname, req,					depth, recurse);				if(rp == nil)					break; 				/* rp->host == nil shouldn't happen, but does */				if(rp->negative || rp->host == nil){					rrfreelist(rp);					rp = nil;					break;				} 				name = rp->host->name;				lock(&dnlock);				if(cn)					rrcat(cn, rp);				else					rrfreelist(rp);				unlock(&dnlock); 				rp = dnresolve1(name, class, type, req,					depth, recurse);			} 		/* distinction between not found and not good */		if(rp == nil && status != nil && dp->respcode != Rok)			*status = dp->respcode;	}	procsetname(procname);	free(procname);	return randomize(rp);} static voidqueryinit(Query *qp, DN *dp, int type, Request *req){	memset(qp, 0, sizeof *qp);	qp->udpfd = qp->tcpfd = qp->tcpctlfd = -1;	qp->dp = dp;	qp->type = type;	if (qp->type != type)		dnslog("queryinit: bogus type %d", type);	qp->req = req;	qp->nsrp = nil;	qp->dest = qp->curdest = nil;	qp->magic = Querymagic;} static voidqueryck(Query *qp){	assert(qp);	assert(qp->magic == Querymagic);} static voidquerydestroy(Query *qp){	queryck(qp);	/* leave udpfd open */	if (qp->tcpfd > 0)		close(qp->tcpfd);	if (qp->tcpctlfd > 0) {		hangup(qp->tcpctlfd);		close(qp->tcpctlfd);	}	free(qp->dest);	memset(qp, 0, sizeof *qp);	/* prevent accidents */	qp->udpfd = qp->tcpfd = qp->tcpctlfd = -1;} static voiddestinit(Dest *p){	memset(p, 0, sizeof *p);	p->magic = Destmagic;} static voiddestck(Dest *p){	assert(p);	assert(p->magic == Destmagic);} /* * if the response to a query hasn't arrived within 100 ms., * it's unlikely to arrive at all.  after 1 s., it's really unlikely. * queries for missing RRs are likely to produce time-outs rather than * negative responses, so cname and aaaa queries are likely to time out, * thus we don't wait very long for them. */static voidnotestats(vlong start, int tmout, int type){	qlock(&stats);	if (tmout) {		stats.tmout++;		if (type == Taaaa)			stats.tmoutv6++;		else if (type == Tcname)			stats.tmoutcname++;	} else {		long wait10ths = NS2MS(nsec() - start) / 100; 		if (wait10ths <= 0)			stats.under10ths[0]++;		else if (wait10ths >= nelem(stats.under10ths))			stats.under10ths[nelem(stats.under10ths) - 1]++;		else			stats.under10ths[wait10ths]++;	}	qunlock(&stats);} static voidnoteinmem(void){	qlock(&stats);	stats.answinmem++;	qunlock(&stats);} /* netquery with given name servers, free ns rrs when done */static intnetqueryns(Query *qp, int depth, RR *nsrp){	int rv; 	qp->nsrp = nsrp;	rv = netquery(qp, depth);	lock(&dnlock);	rrfreelist(nsrp);	unlock(&dnlock);	return rv;} static RR*issuequery(Query *qp, char *name, int class, int depth, int recurse){	char *cp;	DN *nsdp;	RR *rp, *nsrp, *dbnsrp; 	/*	 *  if we're running as just a resolver, query our	 *  designated name servers	 */	if(cfg.resolver){		nsrp = randomize(getdnsservers(class));		if(nsrp != nil)			if(netqueryns(qp, depth+1, nsrp) > Answnone)				return rrlookup(qp->dp, qp->type, OKneg);	} 	/* 	 *  walk up the domain name looking for	 *  a name server for the domain.	 */	for(cp = name; cp; cp = walkup(cp)){		/*		 *  if this is a local (served by us) domain,		 *  return answer		 */		dbnsrp = randomize(dblookup(cp, class, Tns, 0, 0));		if(dbnsrp && dbnsrp->local){			rp = dblookup(name, class, qp->type, 1, dbnsrp->ttl);			lock(&dnlock);			rrfreelist(dbnsrp);			unlock(&dnlock);			return rp;		} 		/*		 *  if recursion isn't set, just accept local		 *  entries		 */		if(recurse == Dontrecurse){			if(dbnsrp) {				lock(&dnlock);				rrfreelist(dbnsrp);				unlock(&dnlock);			}			continue;		} 		/* look for ns in cache */		nsdp = dnlookup(cp, class, 0);		nsrp = nil;		if(nsdp)			nsrp = randomize(rrlookup(nsdp, Tns, NOneg)); 		/* if the entry timed out, ignore it */		if(nsrp && nsrp->ttl < now){			lock(&dnlock);			rrfreelistptr(&nsrp);			unlock(&dnlock);		} 		if(nsrp){			lock(&dnlock);			rrfreelistptr(&dbnsrp);			unlock(&dnlock); 			/* query the name servers found in cache */			if(netqueryns(qp, depth+1, nsrp) > Answnone)				return rrlookup(qp->dp, qp->type, OKneg);		} else if(dbnsrp)			/* try the name servers found in db */			if(netqueryns(qp, depth+1, dbnsrp) > Answnone)				return rrlookup(qp->dp, qp->type, NOneg);	}	return nil;} static RR*dnresolve1(char *name, int class, int type, Request *req, int depth,	int recurse){	Area *area;	DN *dp;	RR *rp;	Query *qp; 	if(debug)		dnslog("[%d] dnresolve1 %s %d %d", getpid(), name, type, class); 	/* only class Cin implemented so far */	if(class != Cin)		return nil; 	dp = dnlookup(name, class, 1); 	/*	 *  Try the cache first	 */	rp = rrlookup(dp, type, OKneg);	if(rp)		if(rp->db){			/* unauthoritative db entries are hints */			if(rp->auth) {				noteinmem();				if(debug)					dnslog("[%d] dnresolve1 %s %d %d: auth rr in db",						getpid(), name, type, class);				return rp;			}		} else			/* cached entry must still be valid */			if(rp->ttl > now)				/* but Tall entries are special */				if(type != Tall || rp->query == Tall) {					noteinmem();					if(debug)						dnslog("[%d] dnresolve1 %s %d %d: rr not in db",							getpid(), name, type, class);					return rp;				}	lock(&dnlock);	rrfreelist(rp);	unlock(&dnlock);	rp = nil;		/* accident prevention */	USED(rp); 	/*	 * try the cache for a canonical name. if found punt	 * since we'll find it during the canonical name search	 * in dnresolve().	 */	if(type != Tcname){		rp = rrlookup(dp, Tcname, NOneg);		lock(&dnlock);		rrfreelist(rp);		unlock(&dnlock);		if(rp){			if(debug)				dnslog("[%d] dnresolve1 %s %d %d: rr from rrlookup for non-cname",					getpid(), name, type, class);			return nil;		}	} 	/*	 * if the domain name is within an area of ours,

Lines 1–500 of 1729.