| #include <u.h> |
| #include <sys/time.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; |
| Udphdr *uh = (Udphdr*)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[Udphdrsize], Maxudp); |
| if(len < 0) |
| abort(); /* "can't convert" */; |
| rrfree(m.qd); |
| return len; |
| } |
| |
| 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 udpreadtimeout(int, Udphdr*, void*, int, int); |
| 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; |
| |
| for(; ; freeanswers(mp)){ |
| now = time(0); |
| if(now >= endtime) |
| return -1; /* timed out */ |
| |
| /* timed read */ |
| len = udpreadtimeout(fd, (Udphdr*)ibuf, ibuf+Udphdrsize, Maxudpin, (endtime-now)*1000); |
| if(len < 0) |
| return -1; /* timed out */ |
| |
| /* convert into internal format */ |
| memset(mp, 0, sizeof(*mp)); |
| err = convM2DNS(&ibuf[Udphdrsize], 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 */ |
| } |
| |
| static int |
| udpreadtimeout(int fd, Udphdr *h, void *data, int n, int ms) |
| { |
| fd_set rd; |
| struct timeval tv; |
| |
| FD_ZERO(&rd); |
| FD_SET(fd, &rd); |
| |
| tv.tv_sec = ms/1000; |
| tv.tv_usec = (ms%1000)*1000; |
| |
| if(select(fd+1, &rd, 0, 0, &tv) != 1) |
| return -1; |
| return udpread(fd, h, data, n); |
| } |
| |
| /* |
| * 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; |
| Udphdr *uh; |
| |
| /* 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); |
| uh = (Udphdr*)obuf; |
| fprint(2, "send %I %I %d %d\n", uh->raddr, uh->laddr, nhgets(uh->rport), nhgets(uh->lport)); |
| if(udpwrite(fd, uh, obuf+Udphdrsize, 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+Udphdrsize); |
| obuf = emalloc(Maxudp+Udphdrsize); |
| |
| /* 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; |
| } |