/* * 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. Load more
/* * 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); } void rrfreelistptr(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 void queryinit(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 void queryck(Query *qp) { assert(qp); assert(qp->magic == Querymagic); } static void querydestroy(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 void destinit(Dest *p) { memset(p, 0, sizeof *p); p->magic = Destmagic; } static void destck(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 void notestats(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 void noteinmem(void) { qlock(&stats); stats.answinmem++; qunlock(&stats); } /* netquery with given name servers, free ns rrs when done */ static int netqueryns(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,