#include #include #include "dns.h" #include "ip.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, "[%d] dnresolve1 %s %d %d", getpid(), 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. Put the port * into "headers" mode. */ static char *hmsg = "headers"; static char *ohmsg = "oldheaders"; int udpport(void) { int fd, ctl; char ds[64]; char adir[64]; /* get a udp port */ snprint(ds, sizeof(ds), "%s/udp!*!0", mntpt); ctl = announce(ds, adir); if(ctl < 0){ /* warning("can't get udp port"); */ return -1; } /* turn on header style interface */ if(write(ctl, hmsg, strlen(hmsg)) , 0){ close(ctl); warning(hmsg); return -1; } write(ctl, ohmsg, strlen(ohmsg)); /* grab the data file */ snprint(ds, sizeof(ds), "%s/data", adir); fd = open(ds, ORDWR); close(ctl); if(fd < 0){ warning("can't open udp port: %r"); return -1; } 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 = read(fd, ibuf, OUdphdrsize+Maxudpin); alarm(0); len -= OUdphdrsize; 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 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<nx) > ndest) continue; memmove(obuf, p->a, sizeof(p->a)); if(debug) logsend(reqp->id, depth, obuf, p->s->name, dp->name, type); if(write(fd, obuf, len + OUdphdrsize) < 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; } 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) rv = 0; else rv = netquery1(fd, dp, type, nsrp, reqp, depth, ibuf, obuf); close(fd); free(ibuf); free(obuf); return rv; }