ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/cmd/ndb/dns.b/
implement DNS;
#
# domain name service
#
# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved.
#
# RFCs: 1034, 1035, 2181, 2308
#
# TO DO:
# server side:
# database; inmyzone; ptr generation; separate zone transfer
# currently doesn't implement loony rules on case
# limit work
# check data
# Call
# ipv6
#
include "sys.m";
sys: Sys;
stderr: ref Sys->FD;
include "draw.m";
include "bufio.m";
include "srv.m";
srv: Srv;
include "ip.m";
ip: IP;
IPaddrlen, IPaddr, IPv4off, Udphdrlen, Udpraddr, Udpladdr, Udprport, Udplport: import ip;
include "arg.m";
include "attrdb.m";
attrdb: Attrdb;
Db, Dbentry, Tuples: import attrdb;
include "ipattr.m";
ipattr: IPattr;
dbattr: import ipattr;
include "keyring.m";
include "security.m";
random: Random;
include "dial.m";
dial: Dial;
DNS: module
{
init: fn(nil: ref Draw->Context, nil: list of string);
};
Reply: adt
{
fid: int;
pid: int;
query: string;
attr: string;
addrs: list of string;
err: string;
};
rlist: list of ref Reply;
dnsfile := "/lib/ndb/local";
myname: string;
mntpt := "/net";
DNSport: con 53;
debug := 0;
referdns := 0;
usehost := 1;
now: int;
servers: list of string;
# domain name from dns/db
domain: string;
dnsdomains: list of string;
init(nil: ref Draw->Context, args: list of string)
{
sys = load Sys Sys->PATH;
stderr = sys->fildes(2);
arg := load Arg Arg->PATH;
if(arg == nil)
cantload(Arg->PATH);
dial = load Dial Dial->PATH;
if(dial == nil)
cantload(Dial->PATH);
arg->init(args);
arg->setusage("dns [-Drh] [-f dnsfile] [-x mntpt]");
svcname := "#sdns";
while((c := arg->opt()) != 0)
case c {
'D' =>
debug = 1;
'f' =>
dnsfile = arg->earg();
'h' =>
usehost = 0;
'r' =>
referdns = 1;
'x' =>
mntpt = arg->earg();
svcname = "#sdns"+svcpt(mntpt);
* =>
arg->usage();
}
args = arg->argv();
if(args != nil)
arg->usage();
arg = nil;
if(usehost){
srv = load Srv Srv->PATH; # hosted Inferno only
if(srv != nil)
srv->init();
}
ip = load IP IP->PATH;
if(ip == nil)
cantload(IP->PATH);
ip->init();
attrdb = load Attrdb Attrdb->PATH;
if(attrdb == nil)
cantload(Attrdb->PATH);
attrdb->init();
ipattr = load IPattr IPattr->PATH;
if(ipattr == nil)
cantload(IPattr->PATH);
ipattr->init(attrdb, ip);
sys->pctl(Sys->NEWPGRP | Sys->FORKFD, nil);
random = load Random Random->PATH;
if(random == nil)
cantload(Random->PATH);
dnsid = random->randomint(Random->ReallyRandom); # avoid clashes
random = nil;
myname = sysname();
stderr = sys->fildes(2);
readservers();
now = time();
sys->remove(svcname+"/dns");
sys->unmount(svcname, mntpt);
publish(svcname);
if(sys->bind(svcname, mntpt, Sys->MBEFORE) < 0)
error(sys->sprint("can't bind #s on %s: %r", mntpt));
file := sys->file2chan(mntpt, "dns");
if(file == nil)
error(sys->sprint("can't make %s/dns: %r", mntpt));
sync := chan of int;
spawn dnscache(sync);
<-sync;
spawn dns(file);
}
publish(dir: string)
{
d := Sys->nulldir;
d.mode = 8r777;
if(sys->wstat(dir, d) < 0)
sys->fprint(sys->fildes(2), "cs: can't publish %s: %r\n", dir);
}
svcpt(s: string): string
{
for(i:=0; i<len s; i++)
if(s[i] == '/')
s[i] = '_';
return s;
}
cantload(s: string)
{
error(sys->sprint("can't load %s: %r", s));
}
error(s: string)
{
sys->fprint(stderr, "dns: %s\n", s);
raise "fail:error";
}
dns(file: ref Sys->FileIO)
{
pidc := chan of int;
donec := chan of ref Reply;
for(;;){
alt {
(nil, buf, fid, wc) := <-file.write =>
now = time();
cleanfid(fid); # each write cancels previous requests
if(wc != nil){
r := ref Reply;
r.fid = fid;
spawn request(r, buf, wc, pidc, donec);
r.pid = <-pidc;
rlist = r :: rlist;
}
(off, nbytes, fid, rc) := <-file.read =>
now = time();
if(rc != nil){
r := findfid(fid);
if(r != nil)
reply(r, off, nbytes, rc);
else
rc <-= (nil, "unknown request");
}
r := <-donec =>
now = time();
r.pid = 0;
if(r.err != nil)
cleanfid(r.fid);
}
}
}
findfid(fid: int): ref Reply
{
for(rl := rlist; rl != nil; rl = tl rl){
r := hd rl;
if(r.fid == fid)
return r;
}
return nil;
}
cleanfid(fid: int)
{
rl := rlist;
rlist = nil;
for(; rl != nil; rl = tl rl){
r := hd rl;
if(r.fid != fid)
rlist = r :: rlist;
else
killgrp(r.pid);
}
}
killgrp(pid: int)
{
if(pid != 0){
fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
if(fd == nil || sys->fprint(fd, "killgrp") < 0)
sys->fprint(stderr, "dns: can't killgrp %d: %r\n", pid);
}
}
request(r: ref Reply, data: array of byte, wc: chan of (int, string), pidc: chan of int, donec: chan of ref Reply)
{
pidc <-= sys->pctl(Sys->NEWPGRP, nil);
query := string data;
for(i := 0; i < len query; i++)
if(query[i] == ' ')
break;
r.query = query[0:i];
for(; i < len query && query[i] == ' '; i++)
;
r.attr = query[i:];
attr := rrtype(r.attr);
if(attr < 0)
r.err = "unknown type";
else
(r.addrs, r.err) = dnslookup(r.query, attr);
if(r.addrs == nil && r.err == nil)
r.err = "not found";
if(r.err != nil){
if(debug)
sys->fprint(stderr, "dns: %s: %s\n", query, r.err);
wc <-= (0, "dns: "+r.err);
} else
wc <-= (len data, nil);
donec <-= r;
}
reply(r: ref Reply, off: int, nbytes: int, rc: chan of (array of byte, string))
{
if(r.err != nil || r.addrs == nil){
rc <-= (nil, r.err);
return;
}
addr: string;
if(r.addrs != nil){
addr = hd r.addrs;
r.addrs = tl r.addrs;
}
off = 0; # this version ignores offsets
# rc <-= reads(r.query+" "+r.attr+" "+addr, off, nbytes);
rc <-= reads(addr, off, nbytes);
}
#
# return the file2chan reply for a read of the given string
#
reads(str: string, off, nbytes: int): (array of byte, string)
{
bstr := array of byte str;
slen := len bstr;
if(off < 0 || off >= slen)
return (nil, nil);
if(off + nbytes > slen)
nbytes = slen - off;
if(nbytes <= 0)
return (nil, nil);
return (bstr[off:off+nbytes], nil);
}
sysname(): string
{
t := rf("/dev/sysname");
if(t != nil)
return t;
t = rf("#e/sysname");
if(t == nil){
s := rf(mntpt+"/ndb");
if(s != nil){
db := Db.sopen(t);
if(db != nil){
(e, nil) := db.find(nil, "sys");
if(e != nil)
t = e.findfirst("sys");
}
}
}
if(t != nil){
fd := sys->open("/dev/sysname", Sys->OWRITE);
if(fd != nil)
sys->fprint(fd, "%s", t);
}
return t;
}
rf(name: string): string
{
fd := sys->open(name, Sys->OREAD);
buf := array[Sys->NAMEMAX] of byte;
n := sys->read(fd, buf, len buf);
if(n <= 0)
return nil;
return string buf[0:n];
}
samefile(d1, d2: Sys->Dir): int
{
# ``it was black ... it was white! it was dark ... it was light! ah yes, i remember it well...''
return d1.dev==d2.dev && d1.dtype==d2.dtype &&
d1.qid.path==d2.qid.path && d1.qid.vers==d2.qid.vers &&
d1.mtime==d2.mtime;
}
#
# database
# dnsdomain= suffix to add to unqualified unrooted names
# dns= dns server to try
# dom= domain name
# ip= IP address
# ns= name server
# soa=
# soa=delegated
# infernosite= set of site-wide parameters
#
#
# basic Domain Name Service resolver
#
laststat := 0; # time last stat'd (to reduce churn)
dnsdb: ref Db;
readservers(): list of string
{
if(laststat != 0 && now < laststat+2*60)
return servers;
laststat = now;
if(dnsdb == nil){
db := Db.open(dnsfile);
if(db == nil){
sys->fprint(stderr, "dns: can't open %s: %r\n", dnsfile);
return nil;
}
dyndb := Db.open(mntpt+"/ndb");
if(dyndb != nil)
dnsdb = dyndb.append(db);
else
dnsdb = db;
}else{
if(!dnsdb.changed())
return servers;
dnsdb.reopen();
}
if((l := dblooknet("sys", myname, "dnsdomain")) == nil)
l = dblook("infernosite", "", "dnsdomain");
dnsdomains = "" :: l;
if((l = dblooknet("sys", myname, "dns")) == nil)
l = dblook("infernosite", "", "dns");
servers = l;
# zones := dblook("soa", "", "dom");
#printlist("zones", zones);
if(debug)
printlist("dnsdomains", dnsdomains);
if(debug)
printlist("servers", servers);
return servers;
}
printlist(w: string, l: list of string)
{
sys->print("%s:", w);
for(; l != nil; l = tl l)
sys->print(" %q", hd l);
sys->print("\n");
}
dblookns(dom: string): list of ref RR
{
domns := dblook("dom", dom, "ns");
hosts: list of ref RR;
for(; domns != nil; domns = tl domns){
s := hd domns;
if(debug)
sys->print("dns db: dom=%s ns=%s\n", dom, s);
ipl: list of ref RR = nil;
addrs := dblook("dom", s, "ip");
for(; addrs != nil; addrs = tl addrs){
a := parseip(hd addrs);
if(a != nil){
ipl = ref RR.A(s, Ta, Cin, now+60, 0, a) :: ipl;
if(debug)
sys->print("dom=%s ip=%s\n", s, hd addrs);
}
}
if(ipl != nil){
# only use ones for which we've got addresses
cachec <-= (ipl, 0);
hosts = ref RR.Host(dom, Tns, Cin, now+60, 0, s) :: hosts;
}
}
if(hosts == nil){
if(debug)
sys->print("dns: no ns for dom=%s in db\n", dom);
return nil;
}
cachec <-= (hosts, 0);
cachec <-= Sync;
return hosts;
}
defaultresolvers(): list of ref NS
{
resolvers := readservers();
al: list of ref RR;
for(; resolvers != nil; resolvers = tl resolvers){
nm := hd resolvers;
a := parseip(nm);
if(a == nil){
# try looking it up as a domain name with an ip address
for(addrs := dblook("dom", nm, "ip"); addrs != nil; addrs = tl addrs){
a = parseip(hd addrs);
if(a != nil)
al = ref RR.A("defaultns", Ta, Cin, now+60, 0, a) :: al;
}
}else
al = ref RR.A("defaultns", Ta, Cin, now+60, 0, a) :: al;
}
if(al == nil){
if(debug)
sys->print("dns: no default resolvers\n");
return nil;
}
return ref NS("defaultns", al, 1, now+60) :: nil;
}
dblook(attr: string, val: string, rattr: string): list of string
{
rl: list of string;
ptr: ref Attrdb->Dbptr;
for(;;){
e: ref Dbentry;
(e, ptr) = dnsdb.findbyattr(ptr, attr, val, rattr);
if(e == nil)
break;
for(l := e.findbyattr(attr, val, rattr); l != nil; l = tl l){
(nil, al) := hd l;
for(; al != nil; al = tl al)
if(!inlist((hd al).val, rl))
rl = (hd al).val :: rl;
}
}
return reverse(rl);
}
#
# starting from the ip= associated with attr=val, search over all
# containing networks for the nearest values of rattr
#
dblooknet(attr: string, val: string, rattr: string): list of string
{
#sys->print("dblooknet: %s=%s -> %s\n", attr, val, rattr);
(results, nil) := ipattr->findnetattrs(dnsdb, attr, val, rattr::nil);
rl: list of string;
for(; results != nil; results = tl results){
(nil, nattrs) := hd results;
for(; nattrs != nil; nattrs = tl nattrs){
na := hd nattrs;
if(na.name == rattr){
for(pairs := na.pairs; pairs != nil; pairs = tl pairs)
if((s := (hd pairs).val) != nil && !inlist(s, rl))
rl = s :: rl;
}
}
}
if(rl == nil)
return dblook(attr, val, rattr);
return reverse(rl);
}
inlist(s: string, l: list of string): int
{
for(; l != nil; l = tl l)
if(hd l == s)
return 1;
return 0;
}
reverse[T](l: list of T): list of T
{
r: list of T;
for(; l != nil; l = tl l)
r = hd l :: r;
return r;
}
append(h: list of string, s: string): list of string
{
if(h == nil)
return s :: nil;
return hd h :: append(tl h, s);
}
#
# subset of RR types
#
Ta: con 1;
Tns: con 2;
Tcname: con 5;
Tsoa: con 6;
Tmb: con 7;
Tptr: con 12;
Thinfo: con 13;
Tmx: con 15;
Tall: con 255;
#
# classes
#
Cin: con 1;
Call: con 255;
#
# opcodes
#
Oquery: con 0<<11; # normal query
Oinverse: con 1<<11; # inverse query
Ostatus: con 2<<11; # status request
Omask: con 16rF<<11; # mask for opcode
#
# response codes
#
Rok: con 0;
Rformat: con 1; # format error
Rserver: con 2; # server failure
Rname: con 3; # bad name
Runimplemented: con 4; # unimplemented operation
Rrefused: con 5; # permission denied, not supported
Rmask: con 16rF; # mask for response
#
# other flags in opcode
#
Fresp: con 1<<15; # message is a response
Fauth: con 1<<10; # true if an authoritative response
Ftrunc: con 1<<9; # truncated message
Frecurse: con 1<<8; # request recursion
Fcanrecurse: con 1<<7; # server can recurse
QR: adt {
name: string;
rtype: int;
class: int;
text: fn(q: self ref QR): string;
};
RR: adt {
name: string;
rtype: int;
class: int;
ttl: int;
flags: int;
pick {
Error =>
reason: string; # cached negative
Host =>
host: string;
Hinfo =>
cpu: string;
os: string;
Mx =>
pref: int;
host: string;
Soa =>
soa: ref SOA;
A or
Other =>
rdata: array of byte;
}
islive: fn(r: self ref RR): int;
outlives: fn(a: self ref RR, b: ref RR): int;
match: fn(a: self ref RR, b: ref RR): int;
text: fn(a: self ref RR): string;
};
SOA: adt {
mname: string;
rname: string;
serial: int;
refresh: int;
retry: int;
expire: int;
minttl: int;
text: fn(nil: self ref SOA): string;
};
DNSmsg: adt {
id: int;
flags: int;
qd: list of ref QR;
an: list of ref RR;
ns: list of ref RR;
ar: list of ref RR;
err: string;
pack: fn(m: self ref DNSmsg, hdrlen: int): array of byte;
unpack: fn(a: array of byte): ref DNSmsg;
text: fn(m: self ref DNSmsg): string;
};
NM: adt {
name: string;
rr: list of ref RR;
stats: ref Stats;
};
Stats: adt {
rtt: int;
};
cachec: chan of (list of ref RR, int);
cache: array of list of ref NM;
Sync: con (nil, 0); # empty list sent to ensure that last cache update done
hash(s: string): array of list of ref NM
{
h := 0;
for(i:=0; i<len s; i++){ # hashpjw
c := s[i];
if(c >= 'A' && c <= 'Z')
c += 'a'-'A';
h = (h<<4) + c;
if((g := h & int 16rF0000000) != 0)
h ^= ((g>>24) & 16rFF) | g;
}
return cache[(h&~(1<<31))%len cache:];
}
lower(s: string): string
{
for(i := 0; i < len s; i++){
c := s[i];
if(c >= 'A' && c <= 'Z'){
n := s;
for(; i < len n; i++){
c = n[i];
if(c >= 'A' && c <= 'Z')
n[i] = c+('a'-'A');
}
return n;
}
}
return s;
}
#
# split rrl into a list of those RRs that match rr and a list of those that don't
#
partrrl(rr: ref RR, rrl: list of ref RR): (list of ref RR, list of ref RR)
{
m: list of ref RR;
nm: list of ref RR;
name := lower(rr.name);
for(; rrl != nil; rrl = tl rrl){
t := hd rrl;
if(t.rtype == rr.rtype && t.class == rr.class &&
(t.name == name || lower(t.name) == name))
m = t :: m;
else
nm = t :: nm;
}
return (m, nm);
}
copyrrl(rrl: list of ref RR): list of ref RR
{
nl: list of ref RR;
for(; rrl != nil; rrl = tl rrl)
nl = ref *hd rrl :: nl;
# return revrrl(rrl);
return rrl; # probably don't care about order
}
dnscache(sync: chan of int)
{
cache = array[32] of list of ref NM;
cachec = chan of (list of ref RR, int);
sync <-= sys->pctl(0, nil);
for(;;){
(rrl, flags) := <-cachec;
#now = time();
List:
while(rrl != nil){
rrset: list of ref RR;
(rrset, rrl) = partrrl(hd rrl, rrl);
rr := hd rrset;
rr.flags = flags;
name := lower(rr.name);
hb := hash(name);
for(ces := hb[0]; ces != nil; ces = tl ces){
ce := hd ces;
if(ce.name == name){
rr.name = ce.name; # share string
x := ce.rr;
ce.rr = insertrrset(ce.rr, rr, rrset);
if(x != ce.rr && debug)
sys->print("insertrr %s:%s\n", name, rrsettext(rrset));
continue List;
}
}
if(debug)
sys->print("newrr %s:%s\n", name, rrsettext(rrset));
hb[0] = ref NM(name, rrset, nil) :: hb[0];
}
}
}
lookcache(name: string, rtype: int, rclass: int): (list of ref RR, string)
{
results: list of ref RR;
name = lower(name);
for(ces := hash(name)[0]; ces != nil; ces = tl ces){
ce := hd ces;
if(ce.name == name){
for(zl := ce.rr; zl != nil; zl = tl zl){
r := hd zl;
if((r.rtype == rtype || r.rtype == Tall || rtype == Tall) && r.class == rclass && r.name == name && r.islive()){
pick ar := r {
Error =>
if(rtype != Tall || ar.reason != "resource does not exist"){
if(debug)
sys->print("lookcache: %s[%s]: !%s\n", name, rrtypename(rtype), ar.reason);
return (nil, ar.reason);
}
* =>
results = ref *r :: results;
}
}
}
}
}
if(debug)
sys->print("lookcache: %s[%s]: %s\n", name, rrtypename(rtype), rrsettext(results));
return (results, nil);
}
#
# insert RRset new in existing list of RRsets rrl
# if that's desirable (it's the whole RRset or nothing, see rfc2181)
#
insertrrset(rrl: list of ref RR, rr: ref RR, new: list of ref RR): list of ref RR
{
# TO DO: expire entries
match := 0;
for(l := rrl; l != nil; l = tl l){
orr := hd l;
if(orr.rtype == rr.rtype && orr.class == rr.class){ # name already known to match
match = 1;
if(!orr.islive())
break; # prefer new, unexpired data
if(tagof rr == tagof RR.Error && tagof orr != tagof RR.Error)
return rrl; # prefer unexpired positive
if(rr.flags & Fauth)
break; # prefer newly-arrived authoritative data
if(orr.flags & Fauth)
return rrl; # prefer authoritative data
if(orr.outlives(rr))
return rrl; # prefer longer-lived data
}
}
if(match){
# strip out existing RR set
l = rrl;
rrl = nil;
for(; l != nil; l = tl l){
orr := hd l;
if((orr.rtype != rr.rtype || orr.class != rr.class) && orr.islive()){
rrl = orr :: rrl;}
}
}
# add new RR set
for(; new != nil; new = tl new){
nrr := hd new;
nrr.name = rr.name;
rrl = nrr :: rrl;
}
return rrl;
}
rrsettext(rrl: list of ref RR): string
{
s := "";
for(; rrl != nil; rrl = tl rrl)
s += " ["+(hd rrl).text()+"]";
return s;
}
QR.text(qr: self ref QR): string
{
s := sys->sprint("%s %s", qr.name, rrtypename(qr.rtype));
if(qr.class != Cin)
s += sys->sprint(" [c=%d]", qr.class);
return s;
}
RR.islive(rr: self ref RR): int
{
return rr.ttl >= now;
}
RR.outlives(a: self ref RR, b: ref RR): int
{
return a.ttl > b.ttl;
}
RR.match(a: self ref RR, b: ref RR): int
{
# compare content, not ttl
return a.rtype == b.rtype && a.class == b.class && a.name == b.name;
}
RR.text(rr: self ref RR): string
{
s := sys->sprint("%s %s", rr.name, rrtypename(rr.rtype));
pick ar := rr {
Host =>
s += sys->sprint("\t%s", ar.host);
Hinfo =>
s += sys->sprint("\t%s %s", ar.cpu, ar.os);
Mx =>
s += sys->sprint("\t%ud %s", ar.pref, ar.host);
Soa =>
s += sys->sprint("\t%s", ar.soa.text());
A =>
if(len ar.rdata == 4){
a := ar.rdata;
s += sys->sprint("\t%d.%d.%d.%d", int a[0], int a[1], int a[2], int a[3]);
}
Error =>
s += sys->sprint("\t!%s", ar.reason);
}
return s;
}
SOA.text(soa: self ref SOA): string
{
return sys->sprint("%s %s %ud %ud %ud %ud %ud", soa.mname, soa.rname,
soa.serial, soa.refresh, soa.retry, soa.expire, soa.minttl);
}
NS: adt {
name: string;
addr: list of ref RR;
canrecur: int;
ttl: int;
};
dnslookup(name: string, attr: int): (list of string, string)
{
case attr {
Ta =>
case dbattr(name) {
"sys" =>
# could apply domains
;
"dom" =>
;
* =>
return (nil, "invalid host name");
}
if(srv != nil){ # try the host's map first
l := srv->iph2a(name);
if(l != nil)
return (fullresult(name, "ip", l), nil);
}
Tptr =>
if(srv != nil){ # try host's map first
l := srv->ipa2h(arpa2addr(name));
if(l != nil)
return (fullresult(name, "ptr", l), nil);
}
}
return dnslookup1(name, attr);
}
fullresult(name: string, attr: string, l: list of string): list of string
{
rl: list of string;
for(; l != nil; l = tl l)
rl = sys->sprint("%s %s\t%s", name, attr, hd l) :: rl;
return reverse(rl);
}
arpa2addr(a: string): string
{
(nil, flds) := sys->tokenize(a, ".");
rl: list of string;
for(; flds != nil && lower(s := hd flds) != "in-addr"; flds = tl flds)
rl = s :: rl;
dom: string;
for(; rl != nil; rl = tl rl){
if(dom != nil)
dom[len dom] = '.';
dom += hd rl;
}
return dom;
}
dnslookup1(label: string, attr: int): (list of string, string)
{
(rrl, err) := fulldnsquery(label, attr, 0);
if(err != nil || rrl == nil)
return (nil, err);
r: list of string;
for(; rrl != nil; rrl = tl rrl)
r = (hd rrl).text() :: r;
return (reverse(r), nil);
}
trimdot(s: string): string
{
while(s != nil && s[len s - 1] == '.')
s = s[0:len s -1];
return s;
}
parent(s: string): string
{
if(s == "")
return ".";
for(i := 0; i < len s; i++)
if(s[i] == '.')
return s[i+1:];
return "";
}
rootservers(): list of ref NS
{
slist := ref NS("a.root-servers.net",
ref RR.A("a.root-servers.net", Ta, Cin, 1<<31, 0,
array[] of {byte 198, byte 41, byte 0, byte 4})::nil, 0, 1<<31) :: nil;
return slist;
}
#
# this broadly follows the algorithm given in RFC 1034
# as adjusted and qualified by several other RFCs.
# `label' is 1034's SNAME, `attr' is `STYPE'
#
# TO DO:
# keep statistics for name servers
fulldnsquery(label: string, attr: int, depth: int): (list of ref RR, string)
{
slist: list of ref NS;
fd: ref Sys->FD;
if(depth > 10)
return (nil, "dns loop");
ncname := 0;
Step1:
for(tries:=0; tries<10; tries++){
# 1. see if in local information, and if so, return it
(x, err) := lookcache(label, attr, Cin);
if(x != nil)
return (x, nil);
if(err != nil)
return (nil, err);
if(attr != Tcname){
if(++ncname > 10)
return (nil, "cname alias loop");
(x, err) = lookcache(label, Tcname, Cin);
if(x != nil){
pick rx := hd x {
Host =>
label = rx.host;
continue;
}
}
}
# 2. find the best servers to ask
slist = nil;
for(d := trimdot(label); d != "."; d = parent(d)){
nsl: list of ref RR;
(nsl, err) = lookcache(d, Tns, Cin);
if(nsl == nil)
nsl = dblookns(d);
# add each to slist; put ones with known addresses first
known: list of ref NS = nil;
for(; nsl != nil; nsl = tl nsl){
pick ns := hd nsl {
Host =>
(addrs, err2) := lookcache(ns.host, Ta, Cin);
if(addrs != nil)
known = ref NS(ns.host, addrs, 0, 1<<31) :: known;
else if(err2 == nil)
slist = ref NS(ns.host, nil, 0, 1<<31) :: slist;
}
}
for(; known != nil; known = tl known)
slist = hd known :: slist;
if(slist != nil)
break;
}
# if no servers, resort to safety belt
if(slist == nil){
slist = defaultresolvers();
if(slist == nil){
slist = rootservers();
if(slist == nil)
return (nil, "no dns servers configured");
}
}
(id, query, err1) := mkquery(attr, Cin, label);
if(err1 != nil){
sys->fprint(stderr, "dns: %s\n", err1);
return (nil, err1);
}
if(debug)
printnslist(sys->sprint("ns for %s: ", d), slist);
# 3. send them queries until one returns a response
for(qset := slist; qset != nil; qset = tl qset){
ns := hd qset;
if(ns.addr == nil){
if(debug)
sys->print("recursive[%d] query for %s address\n", depth+1, ns.name);
(ns.addr, nil) = fulldnsquery(ns.name, Ta, depth+1);
if(ns.addr == nil)
continue;
}
if(fd == nil){
fd = udpport();
if(fd == nil)
return (nil, sys->sprint("%r"));
}
(dm, err2) := udpquery(fd, id, query, ns.name, hd ns.addr);
if(dm == nil){
sys->fprint(stderr, "dns: %s: %s\n", ns.name, err2);
# TO DO: remove from slist
continue;
}
# 4. analyse the response
# a. answers the question or has Rname, cache it and return to client
# b. delegation to other NS? cache and goto step 2.
# c. if response is CNAME and QTYPE!=CNAME change SNAME to the
# canonical name (data) of the CNAME RR and goto step 1.
# d. if response is server failure or otherwise odd, delete server from SLIST
# and goto step 3.
auth := (dm.flags & Fauth) != 0;
soa: ref RR.Soa;
(soa, dm.ns) = soaof(dm.ns);
if((dm.flags & Rmask) != Rok){
# don't repeat the request on an error
# TO DO: should return `best error'
if(tl qset != nil && ((dm.flags & Rmask) != Rname || !auth))
continue;
cause := reason(dm.flags & Rmask);
if(auth && soa != nil){
# rfc2038 says to cache soa with cached negatives, and the
# negative to be retrieved for all attributes if name does not exist
if((ttl := soa.soa.minttl) > 0)
ttl += now;
else
ttl = now+10*60;
a := attr;
if((dm.flags & Rmask) == Rname)
a = Tall;
cachec <-= (ref RR.Error(label, a, Cin, ttl, auth, cause)::soa::nil, auth);
}
return (nil, cause);
}
if(dm.an != nil){
if(1 && dm.ns != nil)
cachec <-= (dm.ns, 0);
if(1 && dm.ar != nil)
cachec <-= (dm.ar, 0);
cachec <-= (dm.an, auth);
cachec <-= Sync;
if(isresponse(dm, attr))
return (dm.an, nil);
if(attr != Tcname && (cn := cnameof(dm)) != nil){
if(++ncname > 10)
return (nil, "cname alias loop");
label = cn;
continue Step1;
}
}
if(auth){
if(soa != nil && (ttl := soa.soa.minttl) > 0)
ttl += now;
else
ttl = now+10*60;
if(soa != nil)
l := soa :: nil;
cachec <-= (ref RR.Error(label, attr, Cin, ttl, auth, "resource does not exist")::l, auth);
return (nil, "resource does not exist");
}
if(isdelegation(dm)){
# cache valid name servers and hints
cachec <-= (dm.ns, 0);
if(dm.ar != nil)
cachec <-= (dm.ar, 0);
cachec <-= Sync;
continue Step1;
}
}
}
return (nil, "server failed");
}
isresponse(dn: ref DNSmsg, attr: int): int
{
if(dn == nil || dn.an == nil)
return 0;
return (hd dn.an).rtype == attr;
}
cnameof(dn: ref DNSmsg): string
{
if(dn != nil && dn.an != nil && (rr := hd dn.an).rtype == Tcname)
pick ar := rr {
Host =>
return ar.host;
}
return nil;
}
soaof(rrl: list of ref RR): (ref RR.Soa, list of ref RR)
{
for(l := rrl; l != nil; l = tl l)
pick rr := hd l {
Soa =>
rest := tl l;
for(; rrl != l; rrl = tl rrl)
if(tagof hd rrl != tagof RR.Soa) # (just in case)
rest = hd rrl :: rest;
return (rr, rest);
}
return (nil, rrl);
}
isdelegation(dn: ref DNSmsg): int
{
if(dn.an != nil)
return 0;
for(al := dn.ns; al != nil; al = tl al)
if((hd al).rtype == Tns)
return 1;
return 0;
}
printnslist(prefix: string, nsl: list of ref NS)
{
s := prefix;
for(; nsl != nil; nsl = tl nsl){
ns := hd nsl;
s += sys->sprint(" [%s %s]", ns.name, rrsettext(ns.addr));
}
sys->print("%s\n", s);
}
#
# DNS message format
#
Udpdnslim: con 512;
Labels: adt {
names: list of (string, int);
new: fn(): ref Labels;
look: fn(labs: self ref Labels, s: string): int;
install: fn(labs: self ref Labels, s: string, o: int);
};
Labels.new(): ref Labels
{
return ref Labels;
}
Labels.look(labs: self ref Labels, s: string): int
{
for(nl := labs.names; nl != nil; nl = tl nl){
(t, o) := hd nl;
if(s == t)
return 16rC000 | o;
}
return 0;
}
Labels.install(labs: self ref Labels, s: string, off: int)
{
labs.names = (s, off) :: labs.names;
}
put2(a: array of byte, o: int, val: int): int
{
if(o < 0)
return o;
if(o + 2 > len a)
return -o;
a[o] = byte (val>>8);
a[o+1] = byte val;
return o+2;
}
put4(a: array of byte, o: int, val: int): int
{
if(o < 0)
return o;
if(o + 4 > len a)
return -o;
a[o] = byte (val>>24);
a[o+1] = byte (val>>16);
a[o+2] = byte (val>>8);
a[o+3] = byte val;
return o+4;
}
puta(a: array of byte, o: int, b: array of byte): int
{
if(o < 0)
return o;
l := len b;
if(l > 255 || o+l+1 > len a)
return -(o+l+1);
a[o++] = byte l;
a[o:] = b;
return o+len b;
}
puts(a: array of byte, o: int, s: string): int
{
return puta(a, o, array of byte s);
}
get2(a: array of byte, o: int): (int, int)
{
if(o < 0)
return (0, o);
if(o + 2 > len a)
return (0, -o);
val := (int a[o] << 8) | int a[o+1];
return (val, o+2);
}
get4(a: array of byte, o: int): (int, int)
{
if(o < 0)
return (0, o);
if(o + 4 > len a)
return (0, -o);
val := (((((int a[o] << 8)| int a[o+1]) << 8) | int a[o+2]) << 8) | int a[o+3];
return (val, o+4);
}
gets(a: array of byte, o: int): (string, int)
{
if(o < 0)
return (nil, o);
if(o+1 > len a)
return (nil, -o);
l := int a[o++];
if(o+l > len a)
return (nil, -o);
return (string a[o:o+l], o+l);
}
putdn(a: array of byte, o: int, name: string, labs: ref Labels): int
{
if(o < 0)
return o;
o0 := o;
while(name != "") {
n := labs.look(name);
if(n != 0){
o = put2(a, o, n);
if(o < 0)
return -o0;
return o;
}
for(l := 0; l < len name && name[l] != '.'; l++)
;
if(o+l+1 > len a)
return -o0;
labs.install(name, o);
a[o++] = byte l;
for(i := 0; i < l; i++)
a[o++] = byte name[i];
for(; l < len name && name[l] == '.'; l++)
;
name = name[l:];
}
if(o >= len a)
return -o0;
a[o++] = byte 0;
return o;
}
getdn(a: array of byte, o: int, depth: int): (string, int)
{
if(depth > 30)
return (nil, -o);
if(o < 0)
return (nil, o);
name := "";
while(o < len a && (l := int a[o++]) != 0) {
if((l & 16rC0) == 16rC0) { # pointer
if(o >= len a)
return (nil, -o);
po := ((l & 16r3F)<<8) | int a[o];
if(po >= len a)
return ("", -o);
o++;
pname: string;
(pname, po) = getdn(a, po, depth+1);
if(po < 1)
return (nil, -o);
name += pname;
break;
}
if((l & 16rC0) != 0)
return (nil, -o); # format error
if(o + l > len a)
return (nil, -o);
name += string a[o:o+l];
o += l;
if(o < len a && a[o] != byte 0)
name += ".";
}
return (lower(name), o);
}
putqrl(a: array of byte, o: int, qrl: list of ref QR, labs: ref Labels): int
{
for(; qrl != nil && o >= 0; qrl = tl qrl){
q := hd qrl;
o = putdn(a, o, q.name, labs);
o = put2(a, o, q.rtype);
o = put2(a, o, q.class);
}
return o;
}
getqrl(nq: int, a: array of byte, o: int): (list of ref QR, int)
{
if(o < 0)
return (nil, o);
qrl: list of ref QR;
for(i := 0; i < nq; i++) {
qd := ref QR;
(qd.name, o) = getdn(a, o, 0);
(qd.rtype, o) = get2(a, o);
(qd.class, o) = get2(a, o);
if(o < 1)
break;
qrl = qd :: qrl;
}
q: list of ref QR;
for(; qrl != nil; qrl = tl qrl)
q = hd qrl :: q;
return (q, o);
}
putrrl(a: array of byte, o: int, rrl: list of ref RR, labs: ref Labels): int
{
if(o < 0)
return o;
for(; rrl != nil; rrl = tl rrl){
rr := hd rrl;
o0 := o;
o = putdn(a, o, rr.name, labs);
o = put2(a, o, rr.rtype);
o = put2(a, o, rr.class);
o = put4(a, o, rr.ttl);
pick ar := rr {
Host =>
o = putdn(a, o, ar.host, labs);
Hinfo =>
o = puts(a, o, ar.cpu);
o = puts(a, o, ar.os);
Mx =>
o = put2(a, o, ar.pref);
o = putdn(a, o, ar.host, labs);
Soa =>
soa := ar.soa;
o = putdn(a, o, soa.mname, labs);
o = putdn(a, o, soa.rname, labs);
o = put4(a, o, soa.serial);
o = put4(a, o, soa.refresh);
o = put4(a, o, soa.retry);
o = put4(a, o, soa.expire);
o = put4(a, o, soa.minttl);
A or
Other =>
dlen := len ar.rdata;
o = put2(a, o, dlen);
if(o < 1)
return -o0;
if(o + dlen > len a)
return -o0;
a[o:] = ar.rdata;
o += dlen;
}
}
return o;
}
getrrl(nr: int, a: array of byte, o: int): (list of ref RR, int)
{
if(o < 0)
return (nil, o);
rrl: list of ref RR;
for(i := 0; i < nr; i++) {
name: string;
rtype, rclass, ttl: int;
(name, o) = getdn(a, o, 0);
(rtype, o) = get2(a, o);
(rclass, o) = get2(a, o);
(ttl, o) = get4(a, o);
if(ttl <= 0)
ttl = 0;
#ttl = 1*60;
ttl += now;
dlen: int;
(dlen, o) = get2(a, o);
if(o < 1)
return (rrl, o);
if(o+dlen > len a)
return (rrl, -(o+dlen));
rr: ref RR;
dname: string;
case rtype {
Tsoa =>
soa := ref SOA;
(soa.mname, o) = getdn(a, o, 0);
(soa.rname, o) = getdn(a, o, 0);
(soa.serial, o) = get4(a, o);
(soa.refresh, o) = get4(a, o);
(soa.retry, o) = get4(a, o);
(soa.expire, o) = get4(a, o);
(soa.minttl, o) = get4(a, o);
rr = ref RR.Soa(name, rtype, rclass, ttl, 0, soa);
Thinfo =>
cpu, os: string;
(cpu, o) = gets(a, o);
(os, o) = gets(a, o);
rr = ref RR.Hinfo(name, rtype, rclass, ttl, 0, cpu, os);
Tmx =>
pref: int;
host: string;
(pref, o) = get2(a, o);
(host, o) = getdn(a, o, 0);
rr = ref RR.Mx(name, rtype, rclass, ttl, 0, pref, host);
Tcname or
Tns or
Tptr =>
(dname, o) = getdn(a, o, 0);
rr = ref RR.Host(name, rtype, rclass, ttl, 0, dname);
Ta =>
rdata := array[dlen] of byte;
rdata[0:] = a[o:o+dlen];
rr = ref RR.A(name, rtype, rclass, ttl, 0, rdata);
o += dlen;
* =>
rdata := array[dlen] of byte;
rdata[0:] = a[o:o+dlen];
rr = ref RR.Other(name, rtype, rclass, ttl, 0, rdata);
o += dlen;
}
rrl = rr :: rrl;
}
r: list of ref RR;
for(; rrl != nil; rrl = tl rrl)
r = (hd rrl) :: r;
return (r, o);
}
DNSmsg.pack(msg: self ref DNSmsg, hdrlen: int): array of byte
{
a := array[Udpdnslim+hdrlen] of byte;
l := hdrlen;
l = put2(a, l, msg.id);
l = put2(a, l, msg.flags);
l = put2(a, l, len msg.qd);
l = put2(a, l, len msg.an);
l = put2(a, l, len msg.ns);
l = put2(a, l, len msg.ar);
labs := Labels.new();
l = putqrl(a, l, msg.qd, labs);
l = putrrl(a, l, msg.an, labs);
l = putrrl(a, l, msg.ns, labs);
l = putrrl(a, l, msg.ar, labs);
if(l < 1)
return nil;
return a[0:l];
}
DNSmsg.unpack(a: array of byte): ref DNSmsg
{
msg := ref DNSmsg;
msg.flags = Rformat;
l := 0;
(msg.id, l) = get2(a, l);
(msg.flags, l) = get2(a, l);
if(l < 0 || l > len a){
msg.err = "length error";
return msg;
}
if(l >= len a)
return msg;
nqd, nan, nns, nar: int;
(nqd, l) = get2(a, l);
(nan, l) = get2(a, l);
(nns, l) = get2(a, l);
(nar, l) = get2(a, l);
if(l >= len a)
return msg;
(msg.qd, l) = getqrl(nqd, a, l);
(msg.an, l) = getrrl(nan, a, l);
(msg.ns, l) = getrrl(nns, a, l);
(msg.ar, l) = getrrl(nar, a, l);
if(l < 1){
sys->fprint(stderr, "l=%d format error\n", l);
msg.err = "format error";
return msg;
}
return msg;
}
DNSmsg.text(msg: self ref DNSmsg): string
{
s := sys->sprint("id=%ud flags=#%ux[%s]\n", msg.id, msg.flags, flagtext(msg.flags));
s += " QR:\n";
for(x := msg.qd; x != nil; x = tl x)
s += "\t"+(hd x).text()+"\n";
s += " AN:\n";
for(l := msg.an; l != nil; l = tl l)
s += "\t"+(hd l).text()+"\n";
s += " NS:\n";
for(l = msg.ns; l != nil; l = tl l)
s += "\t"+(hd l).text()+"\n";
s += " AR:\n";
for(l = msg.ar; l != nil; l = tl l)
s += "\t"+(hd l).text()+"\n";
return s;
}
flagtext(f: int): string
{
s := "";
if(f & Fresp)
s += "R";
if(f & Fauth)
s += "A";
if(f & Ftrunc)
s += "T";
if(f & Frecurse)
s += "r";
if(f & Fcanrecurse)
s += "c";
if((f & Fresp) == 0)
return s;
if(s != "")
s += ",";
return s+reason(f & Rmask);
}
rcodes := array[] of {
Rok => "no error",
Rformat => "format error",
Rserver => "server failure",
Rname => "name does not exist",
Runimplemented => "unimplemented",
Rrefused => "refused",
};
reason(n: int): string
{
if(n < 0 || n > len rcodes)
return sys->sprint("error %d", n);
return rcodes[n];
}
rrtype(s: string): int
{
case s {
"ip" => return Ta;
"ns" => return Tns;
"cname" => return Tcname;
"soa" => return Tsoa;
"ptr" => return Tptr;
"mx" => return Tmx;
"hinfo" => return Thinfo;
"all" or "any" => return Tall;
* => return -1;
}
}
rrtypename(t: int): string
{
case t {
Ta => return "ip";
Tns => return "ns";
Tcname => return "cname";
Tsoa => return "soa";
Tptr => return "ptr";
Tmx => return "mx";
Tall => return "all";
Thinfo => return "hinfo";
* => return string t;
}
}
#
# format of UDP head read and written in `headers' mode
#
Udphdrsize: con Udphdrlen;
dnsid := 1;
mkquery(qtype: int, qclass: int, name: string): (int, array of byte, string)
{
qd := ref QR(name, qtype, qclass);
dm := ref DNSmsg;
dm.id = dnsid++; # doesn't matter if two different procs use it (different fds)
dm.flags = Oquery;
if(referdns || !debug)
dm.flags |= Frecurse;
dm.qd = qd :: nil;
a: array of byte;
a = dm.pack(Udphdrsize);
if(a == nil)
return (0, nil, "dns: bad query message"); # should only happen if a name is ridiculous
for(i:=0; i<Udphdrsize; i++)
a[i] = byte 0;
a[Udprport] = byte (DNSport>>8);
a[Udprport+1] = byte DNSport;
return (dm.id&16rFFFF, a, nil);
}
udpquery(fd: ref Sys->FD, id: int, query: array of byte, sname: string, addr: ref RR): (ref DNSmsg, string)
{
# TO DO: check address and ports?
if(debug)
sys->print("udp query %s\n", sname);
pick ar := addr {
A =>
query[Udpraddr:] = ip->v4prefix[0:IPv4off];
query[Udpraddr+IPv4off:] = ar.rdata[0:4];
* =>
return (nil, "not A resource");
}
dm: ref DNSmsg;
pidc := chan of int;
c := chan of array of byte;
spawn reader(fd, c, pidc);
rpid := <-pidc;
spawn timer(c, pidc);
tpid := <-pidc;
for(ntries := 0; ntries < 8; ntries++){
if(debug){
ipa := query[Udpraddr+IPv4off:];
sys->print("send udp!%d.%d.%d.%d!%d [%d] %d\n", int ipa[0], int ipa[1],
int ipa[2], int ipa[3], get2(query, Udprport).t0, ntries, len query);
}
n := sys->write(fd, query, len query);
if(n != len query)
return (nil, sys->sprint("udp write err: %r"));
buf := <-c;
if(buf != nil){
buf = buf[Udphdrsize:];
dm = DNSmsg.unpack(buf);
if(dm == nil){
kill(tpid);
kill(rpid);
return (nil, "bad udp reply message");
}
if(dm.flags & Fresp && dm.id == id){
if(dm.flags & Ftrunc && dm.ns == nil){
if(debug)
sys->print("id=%d was truncated\n", dm.id);
}else
break;
}else if(debug)
sys->print("id=%d got flags #%ux id %d\n", id, dm.flags, dm.id);
}else if(debug)
sys->print("timeout\n");
}
kill(tpid);
kill(rpid);
if(dm == nil)
return (nil, "no reply");
if(dm.err != nil){
sys->fprint(stderr, "bad reply: %s\n", dm.err);
return (nil, dm.err);
}
if(debug)
sys->print("reply: %s\n", dm.text());
return (dm, nil);
}
reader(fd: ref Sys->FD, c: chan of array of byte, pidc: chan of int)
{
pidc <-= sys->pctl(0, nil);
for(;;){
buf := array[4096+Udphdrsize] of byte;
n := sys->read(fd, buf, len buf);
if(n > 0){
if(debug)
sys->print("rcvd %d\n", n);
c <-= buf[0:n];
}else
c <-= nil;
}
}
timer(c: chan of array of byte, pidc: chan of int)
{
pidc <-= sys->pctl(0, nil);
for(;;){
sys->sleep(5*1000);
c <-= nil;
}
}
kill(pid: int)
{
fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
if(fd != nil)
sys->fprint(fd, "kill");
}
udpport(): ref Sys->FD
{
conn := dial->announce(mntpt+"/udp!*!0");
if(conn == nil)
return nil;
if(sys->fprint(conn.cfd, "headers") < 0){
sys->fprint(stderr, "dns: can't set headers mode: %r\n");
return nil;
}
conn.dfd = sys->open(conn.dir+"/data", Sys->ORDWR);
if(conn.dfd == nil){
sys->fprint(stderr, "dns: can't open %s/data: %r\n", conn.dir);
return nil;
}
return conn.dfd;
}
#
# TCP/IP can be used to get the whole of a truncated message
#
tcpquery(query: array of byte): (ref DNSmsg, string)
{
# TO DO: check request id, ports etc.
ipa := query[Udpraddr+IPv4off:];
addr := sys->sprint("tcp!%d.%d.%d.%d!%d", int ipa[0], int ipa[1], int ipa[2], int ipa[3], DNSport);
conn := dial->dial(addr, nil);
if(conn == nil)
return (nil, sys->sprint("can't dial %s: %r", addr));
query = query[Udphdrsize-2:];
put2(query, 0, len query-2); # replace UDP header by message length
n := sys->write(conn.dfd, query[Udphdrsize:], len query);
if(n != len query)
return (nil, sys->sprint("dns: %s: write err: %r", addr));
buf := readn(conn.dfd, 2); # TCP/DNS record header
(mlen, nil) := get2(buf, 0);
if(mlen < 2 || mlen > 16384)
return (nil, sys->sprint("dns: %s: bad reply msg length=%d", addr, mlen));
buf = readn(conn.dfd, mlen);
if(buf == nil)
return (nil, sys->sprint("dns: %s: read err: %r", addr));
dm := DNSmsg.unpack(buf);
if(dm == nil)
return (nil, "dns: bad reply message");
if(dm.err != nil){
sys->fprint(stderr, "dns: %s: bad reply: %s\n", addr, dm.err);
return (nil, dm.err);
}
return (dm, nil);
}
readn(fd: ref Sys->FD, nb: int): array of byte
{
buf:= array[nb] of byte;
for(n:=0; n<nb;){
m := sys->read(fd, buf[n:], nb-n);
if(m <= 0)
return nil;
n += m;
}
return buf;
}
timefd: ref Sys->FD;
time(): int
{
if(timefd == nil){
timefd = sys->open("/dev/time", Sys->OREAD);
if(timefd == nil)
return 0;
}
buf := array[128] of byte;
sys->seek(timefd, big 0, 0);
n := sys->read(timefd, buf, len buf);
if(n < 0)
return 0;
return int ((big string buf[0:n]) / big 1000000);
}
parseip(s: string): array of byte
{
(ok, a) := IPaddr.parse(s);
if(ok < 0 || !a.isv4())
return nil;
return a.v4();
}