diff options
author | rsc <devnull@localhost> | 2005-12-27 04:30:05 +0000 |
---|---|---|
committer | rsc <devnull@localhost> | 2005-12-27 04:30:05 +0000 |
commit | 3e0d8fb3ea83b2b65a6425c65beda887140f9349 (patch) | |
tree | ce1579f6999c0e4adad35b75c498ddc714b40c1e /src/cmd/ndb/dnresolve.c | |
parent | cff43a06f21a674b2b16e72f8853aac5fd24f48d (diff) | |
download | plan9port-3e0d8fb3ea83b2b65a6425c65beda887140f9349.tar.gz plan9port-3e0d8fb3ea83b2b65a6425c65beda887140f9349.tar.bz2 plan9port-3e0d8fb3ea83b2b65a6425c65beda887140f9349.zip |
add dns
Diffstat (limited to 'src/cmd/ndb/dnresolve.c')
-rwxr-xr-x | src/cmd/ndb/dnresolve.c | 753 |
1 files changed, 753 insertions, 0 deletions
diff --git a/src/cmd/ndb/dnresolve.c b/src/cmd/ndb/dnresolve.c new file mode 100755 index 00000000..253daca4 --- /dev/null +++ b/src/cmd/ndb/dnresolve.c @@ -0,0 +1,753 @@ +#include <u.h> +#include <libc.h> +#include <ip.h> +#include <bio.h> +#include <ndb.h> +#include "dns.h" + +enum +{ + Maxdest= 24, /* maximum destinations for a request message */ + Maxtrans= 3, /* maximum transmissions to a server */ +}; + +static int netquery(DN*, int, RR*, Request*, int); +static RR* dnresolve1(char*, int, int, Request*, int, int); + +char *LOG = "dns"; + +/* + * lookup 'type' info for domain name 'name'. If it doesn't exist, try + * looking it up as a canonical name. + */ +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 nname[Domlen]; + + if(status) + *status = 0; + + /* + * hack for systems that don't have resolve search + * lists. Just look up the simple name in the database. + */ + if(!rooted && strchr(name, '.') == 0){ + rp = nil; + drp = domainlist(class); + for(nrp = drp; nrp != nil; nrp = nrp->next){ + snprint(nname, sizeof(nname), "%s.%s", name, nrp->ptr->name); + rp = dnresolve(nname, class, type, req,cn, depth, recurse, rooted, status); + rrfreelist(rrremneg(&rp)); + if(rp != nil) + break; + } + if(drp != nil) + rrfree(drp); + return rp; + } + + /* + * try the name directly + */ + rp = dnresolve1(name, class, type, req, depth, recurse); + if(rp) + return randomize(rp); + + /* try it as a canonical name if we weren't told the name didn't exist */ + dp = dnlookup(name, class, 0); + if(type != Tptr && dp->nonexistent != Rname){ + for(loops=0; rp == nil && loops < 32; loops++){ + rp = dnresolve1(name, class, Tcname, req, depth, recurse); + if(rp == nil) + break; + + if(rp->negative){ + rrfreelist(rp); + rp = nil; + break; + } + + name = rp->host->name; + if(cn) + rrcat(cn, rp); + else + rrfreelist(rp); + + rp = dnresolve1(name, class, type, req, depth, recurse); + } + } + + /* distinction between not found and not good */ + if(rp == 0 && status != 0 && dp->nonexistent != 0) + *status = dp->nonexistent; + + return randomize(rp); +} + +static RR* +dnresolve1(char *name, int class, int type, Request *req, int depth, int recurse) +{ + DN *dp, *nsdp; + RR *rp, *nsrp, *dbnsrp; + char *cp; + + if(debug) + syslog(0, LOG, "dnresolve1 %s %d %d", name, type, class); + + /* only class Cin implemented so far */ + if(class != Cin) + return 0; + + dp = dnlookup(name, class, 1); + + /* + * Try the cache first + */ + rp = rrlookup(dp, type, OKneg); + if(rp){ + if(rp->db){ + /* unauthenticated db entries are hints */ + if(rp->auth) + return rp; + } else { + /* cached entry must still be valid */ + if(rp->ttl > now){ + /* but Tall entries are special */ + if(type != Tall || rp->query == Tall) + return rp; + } + } + } + rrfreelist(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); + rrfreelist(rp); + if(rp) + return 0; + } + + /* + * if we're running as just a resolver, go to our + * designated name servers + */ + if(resolver){ + nsrp = randomize(getdnsservers(class)); + if(nsrp != nil) { + if(netquery(dp, type, nsrp, req, depth+1)){ + rrfreelist(nsrp); + return rrlookup(dp, type, OKneg); + } + rrfreelist(nsrp); + } + } + + /* + * 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, type, 1, dbnsrp->ttl); + rrfreelist(dbnsrp); + return rp; + } + + /* + * if recursion isn't set, just accept local + * entries + */ + if(recurse == Dontrecurse){ + if(dbnsrp) + rrfreelist(dbnsrp); + 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){ + rrfreelist(nsrp); + nsrp = nil; + } + + if(nsrp){ + rrfreelist(dbnsrp); + + /* try the name servers found in cache */ + if(netquery(dp, type, nsrp, req, depth+1)){ + rrfreelist(nsrp); + return rrlookup(dp, type, OKneg); + } + rrfreelist(nsrp); + continue; + } + + /* use ns from db */ + if(dbnsrp){ + /* try the name servers found in db */ + if(netquery(dp, type, dbnsrp, req, depth+1)){ + /* we got an answer */ + rrfreelist(dbnsrp); + return rrlookup(dp, type, NOneg); + } + rrfreelist(dbnsrp); + } + } + + /* settle for a non-authoritative answer */ + rp = rrlookup(dp, type, OKneg); + if(rp) + return rp; + + /* noone answered. try the database, we might have a chance. */ + return dblookup(name, class, type, 0, 0); +} + +/* + * walk a domain name one element to the right. return a pointer to that element. + * in other words, return a pointer to the parent domain name. + */ +char* +walkup(char *name) +{ + char *cp; + + cp = strchr(name, '.'); + if(cp) + return cp+1; + else if(*name) + return ""; + else + return 0; +} + +/* + * Get a udpport for requests and replies. + */ +int +udpport(void) +{ + int fd; + + if((fd = dial("udp!0!53", nil, nil, nil)) < 0) + warning("can't get udp port"); + return fd; +} + +int +mkreq(DN *dp, int type, uchar *buf, int flags, ushort reqno) +{ + DNSmsg m; + int len; + OUdphdr *uh = (OUdphdr*)buf; + + /* stuff port number into output buffer */ + memset(uh, 0, sizeof(*uh)); + hnputs(uh->rport, 53); + + /* make request and convert it to output format */ + memset(&m, 0, sizeof(m)); + m.flags = flags; + m.id = reqno; + m.qd = rralloc(type); + m.qd->owner = dp; + m.qd->type = type; + len = convDNS2M(&m, &buf[OUdphdrsize], Maxudp); + if(len < 0) + abort(); /* "can't convert" */; + rrfree(m.qd); + return len; +} + +/* for alarms in readreply */ +static void +ding(void *x, char *msg) +{ + USED(x); + if(strcmp(msg, "alarm") == 0) + noted(NCONT); + else + noted(NDFLT); +} + +static void +freeanswers(DNSmsg *mp) +{ + rrfreelist(mp->qd); + rrfreelist(mp->an); + rrfreelist(mp->ns); + rrfreelist(mp->ar); +} + +/* + * read replies to a request. ignore any of the wrong type. wait at most 5 seconds. + */ +static int +readreply(int fd, DN *dp, int type, ushort req, + uchar *ibuf, DNSmsg *mp, ulong endtime, Request *reqp) +{ + char *err; + int len; + ulong now; + RR *rp; + + notify(ding); + + for(; ; freeanswers(mp)){ + now = time(0); + if(now >= endtime) + return -1; /* timed out */ + + /* timed read */ + alarm((endtime - now) * 1000); + len = udpread(fd, (OUdphdr*)ibuf, ibuf+OUdphdrsize, Maxudpin); + alarm(0); + if(len < 0) + return -1; /* timed out */ + + /* convert into internal format */ + memset(mp, 0, sizeof(*mp)); + err = convM2DNS(&ibuf[OUdphdrsize], len, mp); + if(err){ + syslog(0, LOG, "input err %s: %I", err, ibuf); + continue; + } + if(debug) + logreply(reqp->id, ibuf, mp); + + /* answering the right question? */ + if(mp->id != req){ + syslog(0, LOG, "%d: id %d instead of %d: %I", reqp->id, + mp->id, req, ibuf); + continue; + } + if(mp->qd == 0){ + syslog(0, LOG, "%d: no question RR: %I", reqp->id, ibuf); + continue; + } + if(mp->qd->owner != dp){ + syslog(0, LOG, "%d: owner %s instead of %s: %I", reqp->id, + mp->qd->owner->name, dp->name, ibuf); + continue; + } + if(mp->qd->type != type){ + syslog(0, LOG, "%d: type %d instead of %d: %I", reqp->id, + mp->qd->type, type, ibuf); + continue; + } + + /* remember what request this is in answer to */ + for(rp = mp->an; rp; rp = rp->next) + rp->query = type; + + return 0; + } + + return 0; /* never reached */ +} + +/* + * return non-0 if first list includes second list + */ +int +contains(RR *rp1, RR *rp2) +{ + RR *trp1, *trp2; + + for(trp2 = rp2; trp2; trp2 = trp2->next){ + for(trp1 = rp1; trp1; trp1 = trp1->next){ + if(trp1->type == trp2->type) + if(trp1->host == trp2->host) + if(trp1->owner == trp2->owner) + break; + } + if(trp1 == 0) + return 0; + } + + return 1; +} + + +typedef struct Dest Dest; +struct Dest +{ + uchar a[IPaddrlen]; /* ip address */ + DN *s; /* name server */ + int nx; /* number of transmissions */ + int code; +}; + + +/* + * return multicast version if any + */ +int +ipisbm(uchar *ip) +{ + if(isv4(ip)){ + if(ip[IPv4off] >= 0xe0 && ip[IPv4off] < 0xf0) + return 4; + if(ipcmp(ip, IPv4bcast) == 0) + return 4; + } else { + if(ip[0] == 0xff) + return 6; + } + return 0; +} + +/* + * Get next server address + */ +static int +serveraddrs(RR *nsrp, Dest *dest, int nd, int depth, Request *reqp) +{ + RR *rp, *arp, *trp; + Dest *cur; + + if(nd >= Maxdest) + return 0; + + /* + * look for a server whose address we already know. + * if we find one, mark it so we ignore this on + * subsequent passes. + */ + arp = 0; + for(rp = nsrp; rp; rp = rp->next){ + assert(rp->magic == RRmagic); + if(rp->marker) + continue; + arp = rrlookup(rp->host, Ta, NOneg); + if(arp){ + rp->marker = 1; + break; + } + arp = dblookup(rp->host->name, Cin, Ta, 0, 0); + if(arp){ + rp->marker = 1; + break; + } + } + + /* + * if the cache and database lookup didn't find any new + * server addresses, try resolving one via the network. + * Mark any we try to resolve so we don't try a second time. + */ + if(arp == 0){ + for(rp = nsrp; rp; rp = rp->next){ + if(rp->marker) + continue; + rp->marker = 1; + + /* + * avoid loops looking up a server under itself + */ + if(subsume(rp->owner->name, rp->host->name)) + continue; + + arp = dnresolve(rp->host->name, Cin, Ta, reqp, 0, depth+1, Recurse, 1, 0); + rrfreelist(rrremneg(&arp)); + if(arp) + break; + } + } + + /* use any addresses that we found */ + for(trp = arp; trp; trp = trp->next){ + if(nd >= Maxdest) + break; + cur = &dest[nd]; + parseip(cur->a, trp->ip->name); + if(ipisbm(cur->a)) + continue; + cur->nx = 0; + cur->s = trp->owner; + cur->code = Rtimeout; + nd++; + } + rrfreelist(arp); + return nd; +} + +/* + * cache negative responses + */ +static void +cacheneg(DN *dp, int type, int rcode, RR *soarr) +{ + RR *rp; + DN *soaowner; + ulong ttl; + + /* no cache time specified, don' make anything up */ + if(soarr != nil){ + if(soarr->next != nil){ + rrfreelist(soarr->next); + soarr->next = nil; + } + soaowner = soarr->owner; + } else + soaowner = nil; + + /* the attach can cause soarr to be freed so mine it now */ + if(soarr != nil && soarr->soa != nil) + ttl = soarr->soa->minttl+now; + else + ttl = 5*Min; + + /* add soa and negative RR to the database */ + rrattach(soarr, 1); + + rp = rralloc(type); + rp->owner = dp; + rp->negative = 1; + rp->negsoaowner = soaowner; + rp->negrcode = rcode; + rp->ttl = ttl; + rrattach(rp, 1); +} + +/* + * query name servers. If the name server returns a pointer to another + * name server, recurse. + */ +static int +netquery1(int fd, DN *dp, int type, RR *nsrp, Request *reqp, int depth, uchar *ibuf, uchar *obuf) +{ + int ndest, j, len, replywaits, rv; + ushort req; + RR *tp, *soarr; + Dest *p, *l, *np; + DN *ndp; + Dest dest[Maxdest]; + DNSmsg m; + ulong endtime; + + /* pack request into a message */ + req = rand(); + len = mkreq(dp, type, obuf, Frecurse|Oquery, req); + + /* no server addresses yet */ + l = dest; + + /* + * transmit requests and wait for answers. + * at most Maxtrans attempts to each address. + * each cycle send one more message than the previous. + */ + for(ndest = 1; ndest < Maxdest; ndest++){ + p = dest; + + endtime = time(0); + if(endtime >= reqp->aborttime) + break; + + /* get a server address if we need one */ + if(ndest > l - p){ + j = serveraddrs(nsrp, dest, l - p, depth, reqp); + l = &dest[j]; + } + + /* no servers, punt */ + if(l == dest) + break; + + /* send to first 'ndest' destinations */ + j = 0; + for(; p < &dest[ndest] && p < l; p++){ + /* skip destinations we've finished with */ + if(p->nx >= Maxtrans) + continue; + + j++; + + /* exponential backoff of requests */ + if((1<<p->nx) > ndest) + continue; + + memmove(obuf, p->a, sizeof(p->a)); + if(debug) + logsend(reqp->id, depth, obuf, p->s->name, + dp->name, type); +{Udphdr *uh = (Udphdr*)obuf; +print("send %I %I %d %d\n", uh->raddr, uh->laddr, nhgets(uh->rport), nhgets(uh->lport)); +} + if(udpwrite(fd, (OUdphdr*)obuf, obuf+OUdphdrsize, len) < 0) + warning("sending udp msg %r"); + p->nx++; + } + if(j == 0) + break; /* no destinations left */ + + /* wait up to 5 seconds for replies */ + endtime = time(0) + 5; + if(endtime > reqp->aborttime) + endtime = reqp->aborttime; + + for(replywaits = 0; replywaits < ndest; replywaits++){ + if(readreply(fd, dp, type, req, ibuf, &m, endtime, reqp) < 0) + break; /* timed out */ + + /* find responder */ + for(p = dest; p < l; p++) + if(memcmp(p->a, ibuf, sizeof(p->a)) == 0) + break; + + /* remove all addrs of responding server from list */ + for(np = dest; np < l; np++) + if(np->s == p->s) + p->nx = Maxtrans; + + /* ignore any error replies */ + if((m.flags & Rmask) == Rserver){ + rrfreelist(m.qd); + rrfreelist(m.an); + rrfreelist(m.ar); + rrfreelist(m.ns); + if(p != l) + p->code = Rserver; + continue; + } + + /* ignore any bad delegations */ + if(m.ns && baddelegation(m.ns, nsrp, ibuf)){ + rrfreelist(m.ns); + m.ns = nil; + if(m.an == nil){ + rrfreelist(m.qd); + rrfreelist(m.ar); + if(p != l) + p->code = Rserver; + continue; + } + } + + + /* remove any soa's from the authority section */ + soarr = rrremtype(&m.ns, Tsoa); + + /* incorporate answers */ + if(m.an) + rrattach(m.an, (m.flags & Fauth) ? 1 : 0); + if(m.ar) + rrattach(m.ar, 0); + if(m.ns){ + ndp = m.ns->owner; + rrattach(m.ns, 0); + } else + ndp = 0; + + /* free the question */ + if(m.qd) + rrfreelist(m.qd); + + /* + * Any reply from an authoritative server, + * or a positive reply terminates the search + */ + if(m.an != nil || (m.flags & Fauth)){ + if(m.an == nil && (m.flags & Rmask) == Rname) + dp->nonexistent = Rname; + else + dp->nonexistent = 0; + + /* + * cache any negative responses, free soarr + */ + if((m.flags & Fauth) && m.an == nil) + cacheneg(dp, type, (m.flags & Rmask), soarr); + else + rrfreelist(soarr); + return 1; + } + rrfreelist(soarr); + + /* + * if we've been given better name servers + * recurse + */ + if(m.ns){ + tp = rrlookup(ndp, Tns, NOneg); + if(!contains(nsrp, tp)){ + rv = netquery(dp, type, tp, reqp, depth+1); + rrfreelist(tp); + return rv; + } else + rrfreelist(tp); + } + } + } + + /* if all servers returned failure, propogate it */ + dp->nonexistent = Rserver; + for(p = dest; p < l; p++) + if(p->code != Rserver) + dp->nonexistent = 0; + + return 0; +} + +typedef struct Qarg Qarg; +struct Qarg +{ + DN *dp; + int type; + RR *nsrp; + Request *reqp; + int depth; +}; + + +static int +netquery(DN *dp, int type, RR *nsrp, Request *reqp, int depth) +{ + uchar *obuf; + uchar *ibuf; + RR *rp; + int fd, rv; + + if(depth > 12) + return 0; + + /* use alloced buffers rather than ones from the stack */ + ibuf = emalloc(Maxudpin+OUdphdrsize); + obuf = emalloc(Maxudp+OUdphdrsize); + + slave(reqp); + + /* prepare server RR's for incremental lookup */ + for(rp = nsrp; rp; rp = rp->next) + rp->marker = 0; + + fd = udpport(); + if(fd < 0) + return 0; + rv = netquery1(fd, dp, type, nsrp, reqp, depth, ibuf, obuf); + close(fd); + free(ibuf); + free(obuf); + + return rv; +} |