blob: 979abe4ebda01f13a56daabbfde8dcdbdb56cccc [file] [log] [blame]
#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;
}