aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/ndb/dnresolve.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/ndb/dnresolve.c')
-rwxr-xr-xsrc/cmd/ndb/dnresolve.c753
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;
+}