ref: 7c4d2abbe14d3d0e9d2f5872e0964677c5dc783d
dir: /lib/std/resolve+posixy.myr/
use sys
use "alloc"
use "chartype"
use "die"
use "endian"
use "extremum"
use "hashfuncs"
use "htab"
use "ipparse"
use "now"
use "option"
use "result"
use "slcp"
use "sldup"
use "sleq"
use "slpush"
use "slurp"
use "strfind"
use "strsplit"
use "strstrip"
use "threadhooks"
use "types"
use "utf"
use "fmt"
pkg std =
type rectype = union
`DnsA /* host address */
`DnsNS /* authoritative name server */
`DnsCNAME /* canonical name for an alias */
`DnsSOA /* marks the start of a zone of authority */
`DnsWKS /* well known service description */
`DnsPTR /* domain name pointer */
`DnsHINFO /* host information */
`DnsMINFO /* mailbox or mail list information */
`DnsMX /* mail exchange */
`DnsTXT /* text strings */
`DnsAAAA /* ipv6 host address */
`DnsInval /* invalid */
;;
type resolveerr = union
`Timeout
`Badconn
`Badhost
`Badsrv
`Badquery
`Badresp
;;
type hostinfo = struct
fam : sys.sockfam
stype : sys.socktype
ttl : uint32
addr : netaddr
stale : time
rtype : rectype
;;
const resolve : (host : byte[:] -> result(hostinfo[:], resolveerr))
const resolvemx : (host : byte[:] -> result(hostinfo[:], resolveerr))
const resolverec : (host : byte[:], t : rectype[:] -> result(hostinfo[:], resolveerr))
;;
const Hostfile = "/etc/hosts"
const Resolvfile = "/etc/resolv.conf"
const Timeout = 2_000
var initdone : bool = false
var hostmap : htab(byte[:], hostinfo)#
var dnscache : htab(byte[:], hostinfo)#
var search : byte[:][:]
var nameservers : netaddr[:]
const resolve = {host
/*TODO: v4 and v6 */
-> resolverec(host, [`DnsA, `DnsAAAA][:])
}
const resolvemx = {host
-> resolverec(host, [`DnsMX][:])
}
const resolverec = {host, tl
if !initdone /* avoid extra contention */
lock(netlck)
if !initdone
hostmap = mkht()
dnscache = mkht()
loadhosts()
loadresolv()
initdone = true
;;
unlock(netlck)
;;
for t : tl
match findhost(host, rectype(t))
| `Some hinf:
-> `Ok sldup([hinf][:])
| `None:
-> dnsresolve(host, rectype(t))
;;
;;
-> `Err `Badquery
}
const findhost = {host, t
var h
lock(netlck)
match htget(dnscache, host)
| `std.Some inf:
if rectype(inf.rtype) != t
h = `std.None
elif inf.stale > std.now()
h = `std.Some inf
else
h = htget(hostmap, host)
;;
| `std.None:
h = htget(hostmap, host)
;;
unlock(netlck)
-> h
}
const loadhosts = {
var h
var lines
match slurp(Hostfile)
| `Ok d: h = d
| `Err m: -> void
;;
lines = strsplit(h, "\n")
for l : lines
/* trim comment */
match strfind(l, "#")
| `Some idx: l = l[:idx]
| `None: /* whole line */
;;
match word(l)
| `Some (ip, rest):
match ipparse(ip)
| `Some addr:
addhosts(addr, ip, rest)
| `None:
/*
invalid addresses are ignored: we don't want to break stuff
with invalid or unsupported addresses
*/
;;
| `None:
;;
;;
slfree(lines)
slfree(h)
}
const addhosts = {addr, as, str
var hinf
var fam
match addr
| `Ipv4 _: fam = sys.Afinet
| `Ipv6 _: fam = sys.Afinet6
;;
while true
match word(str)
| `Some (name, rest):
str = rest
if hthas(hostmap, name)
continue
;;
hinf = [
.fam=fam,
.stype = 0,
.ttl = 0,
.addr = addr
]
htput(hostmap, sldup(name), hinf)
| `None:
-> void
;;
;;
}
const loadresolv = {
var h
var lines
match slurp(Resolvfile)
| `Ok d: h = d
| `Err m: -> void
;;
lines = strsplit(h, "\n")
for l : lines
match strfind(l, "#")
| `Some _idx: l = l[:_idx]
| `None:
;;
match word(l)
| `Some ("nameserver", srv):
addns(srv)
| `Some (_, rest):
/* invalid or unrecognized commands */
| `None:
/* unrecognized lines */
;;
;;
slfree(lines)
slfree(h)
}
const addns = {rest
match word(rest)
| `Some (name, _):
match ipparse(name)
| `Some addr:
slpush(&nameservers, addr)
| `None:
/* nothing */
;;
| `None:
/* nothing */
;;
}
const word = {s
var c, len
len = 0
s = strfstrip(s)
for c = decode(s[len:]); c != Badchar && !isblank(c); c = decode(s[len:])
len += charlen(c)
;;
if len == 0
-> `None
else
-> `Some (s[:len], s[len:])
;;
}
const dnsresolve = {host, rt
var nsrv, r
if !valid(host)
-> `Err (`Badhost)
;;
/* FIXME: Assumption: nameservers is not modified by other threads */
for ns : nameservers
nsrv = dnsconnect(ns)
if nsrv >= 0
r = dnsquery(nsrv, host, rt)
sys.close(nsrv)
-> r
;;
;;
-> `Err (`Badsrv)
}
const dnsconnect = {ns
match ns
| `Ipv4 addr: -> dnsconnectv4(addr)
| `Ipv6 addr: die("don't support ipv6 yet\n")
;;
}
const dnsconnectv4 = {addr
var sa : sys.sockaddr_in
var s
var status
s = sys.socket(sys.Afinet, sys.Sockdgram, 0)
if s < 0
-> -1
;;
sa = [
.fam = sys.Afinet,
.port = hosttonet(53),
.addr = addr,
]
status = sys.connect(s, (&sa : sys.sockaddr#), sizeof(sys.sockaddr_in))
if status < 0
-> -1
;;
-> s
}
const dnsquery = {srv, host, t
var id
id = tquery(srv, host, t)
-> rquery(srv, host, id)
}
const Qr : uint16 = 1 << 0
const Aa : uint16 = 1 << 5
const Tc : uint16 = 1 << 6
const Rd : uint16 = 1 << 7
const Ra : uint16 = 1 << 8
var nextid : uint16 = 42
const tquery = {srv, host, t
var pkt : byte[512] /* big enough */
var off : size
/* header */
off = 0
nextid++
off += pack16(pkt[:], off, nextid) /* id */
off += pack16(pkt[:], off, Ra) /* flags */
off += pack16(pkt[:], off, 1) /* qdcount */
off += pack16(pkt[:], off, 0) /* ancount */
off += pack16(pkt[:], off, 0) /* nscount */
off += pack16(pkt[:], off, 0) /* arcount */
/* query */
off += packname(pkt[:], off, host) /* host */
off += pack16(pkt[:], off, (t : uint16)) /* qtype: a record */
off += pack16(pkt[:], off, 0x1) /* qclass: inet4 */
sys.write(srv, pkt[:off])
-> nextid
}
const rquery = {srv, host, id
var pktbuf : byte[1024]
var pkt, pfd, giveup
var r, n, inf
giveup = std.now() + 1000*Timeout
:again
pfd = [
[.fd=srv, .events=sys.Pollin, .revents=0]
][:]
r = sys.poll(pfd[:], (giveup - std.now() : int)/1000)
if r < 0
-> `Err `Badconn
elif r == 0
-> `Err `Timeout
else
n = sys.read(srv, pktbuf[:])
if n < 0
-> `Err `Badconn
;;
pkt = pktbuf[:n]
inf = hosts(pkt, host, id)
match inf
| `std.Err `Badresp:
goto again
| _:
-> inf
;;
;;
}
const hosts = {pkt, host, id
var off, ni
var r, n, q, a, t, ttl
var hinf : hostinfo[:]
var v6dat : byte[16]
off = 0
/* parse header */
(r, off) = unpack16(pkt, off) /* id */
if r != id
-> `Err `Badresp
;;
(_, off) = unpack16(pkt, off) /* flags */
(q, off) = unpack16(pkt, off) /* qdcount */
(a, off) = unpack16(pkt, off) /* ancount */
(_, off) = unpack16(pkt, off) /* nscount */
(_, off) = unpack16(pkt, off) /* arcount */
/* skip past query records */
for var i = 0; i < q; i++
off = skipname(pkt, off) /* name */
(_, off) = unpack16(pkt, off) /* type */
(_, off) = unpack16(pkt, off) /* class */
;;
/* parse answer records */
ni = 0
hinf = slalloc((a : size))
for var i = 0; i < a; i++
off = skipname(pkt, off) /* name */
(t, off) = unpack16(pkt, off) /* type */
(_, off) = unpack16(pkt, off) /* class */
(ttl, off) = unpack32(pkt, off) /* ttl */
(n, off) = unpack16(pkt, off) /* rdatalen */
hinf[ni].ttl = ttl
hinf[ni].stale = (ttl * 1_000_000 : time) + std.now()
hinf[ni].rtype = id2type(t)
match hinf[ni].rtype
| `DnsA:
/* the thing we're interested in: our IP address */
hinf[ni].addr = `Ipv4 [pkt[off], pkt[off+1], pkt[off+2], pkt[off+3]]
off += 4;
ni++
| `DnsAAAA:
std.slcp(v6dat[:], pkt[off:off+16])
hinf[ni].addr = `Ipv6 v6dat
off += 16
ni++
| _:
off += (n : std.size)
;;
;;
lock(netlck)
if ni != 0 && hinf[0].ttl != 0
std.htput(dnscache, std.sldup(host), hinf[0])
;;
unlock(netlck)
-> `Ok hinf[:ni]
}
const skipname = {pkt, off
var sz
for sz = (pkt[off] : size); sz != 0; sz = (pkt[off] : size)
/* ptr is 2 bytes */
if sz & 0xC0 == 0xC0
-> off + 2
else
off += sz + 1
;;
;;
-> off + 1
}
const pack16 = {buf, off, v
buf[off] = ((v & 0xff00) >> 8 : byte)
buf[off+1] = ((v & 0x00ff) >> 0 : byte)
-> sizeof(uint16) /* we always write one uint16 */
}
const unpack16 = {buf, off
var v
v = 0
v |= (buf[off + 0] : uint16) << 8
v |= (buf[off + 1] : uint16) << 0
-> (v, off+sizeof(uint16))
}
const unpack32 = {buf, off
var v
v = (buf[off] : uint32) << 24
v |= (buf[off+1] : uint32) << 32
v |= (buf[off+2] : uint32) << 8
v |= (buf[off+3] : uint32)
-> (v, off+sizeof(uint32))
}
const packname = {buf, off : size, host
var start
var last
start = off
last = 0
for var i = 0; i < host.len; i++
if host[i] == ('.' : byte)
off += addseg(buf, off, host[last:i])
last = i + 1
;;
;;
if host[host.len - 1] != ('.' : byte)
off += addseg(buf, off, host[last:])
;;
off += addseg(buf, off, "") /* null terminating segment */
-> off - start
}
const addseg = {buf, off, str
buf[off] = (str.len : byte)
slcp(buf[off + 1 : off + str.len + 1], str)
-> str.len + 1
}
const valid = {host : byte[:]
var i
var seglen
/* maximum length: 255 chars */
if host.len > 255
-> false
;;
seglen = 0
for i = 0; i < host.len; i++
if host[i] == ('.' : byte)
seglen = 0
;;
if seglen > 63
-> false
;;
if host[i] & 0x80 != 0
-> false
;;
;;
-> true
}
const id2type = {rtype
match rtype
| 1: -> `DnsA
| 2: -> `DnsNS
| 5: -> `DnsCNAME
| 6: -> `DnsSOA
| 11: -> `DnsWKS
| 12: -> `DnsPTR
| 13: -> `DnsHINFO
| 14: -> `DnsMINFO
| 15: -> `DnsMX
| 16: -> `DnsTXT
| 28: -> `DnsAAAA
| _: -> `DnsInval
;;
}
const rectype = {rtype
match rtype
| `DnsA: -> 1 /* host address */
| `DnsNS: -> 2 /* authoritative name server */
| `DnsCNAME: -> 5 /* canonical name for an alias */
| `DnsSOA: -> 6 /* marks the start of a zone of authority */
| `DnsWKS: -> 11 /* well known service description */
| `DnsPTR: -> 12 /* domain name pointer */
| `DnsHINFO: -> 13 /* host information */
| `DnsMINFO: -> 14 /* mailbox or mail list information */
| `DnsMX: -> 15 /* mail exchange */
| `DnsTXT: -> 16 /* text strings */
| `DnsAAAA: -> 28 /* ipv6 host address */
| `DnsInval: -> -1
;;
}