ref: 21b91ad9fa7ae830272dc67fbe3150dc30921702
dir: /libstd/resolve+posixy.myr/
use sys use "alloc.use" use "chartype.use" use "die.use" use "endian.use" use "result.use" use "extremum.use" use "hashfuncs.use" use "htab.use" use "ipparse.use" use "option.use" use "slcp.use" use "sleq.use" use "slpush.use" use "slurp.use" use "strfind.use" use "strsplit.use" use "strstrip.use" use "types.use" use "utf.use" pkg std = type rectype = uint16 const DnsA : rectype = 1 /* host address */ const DnsNS : rectype = 2 /* authoritative name server */ const DnsMD : rectype = 3 /* mail destination (Obsolete - use MX) */ const DnsMF : rectype = 4 /* mail forwarder (Obsolete - use MX) */ const DnsCNAME : rectype = 5 /* canonical name for an alias */ const DnsSOA : rectype = 6 /* marks the start of a zone of authority */ const DnsMB : rectype = 7 /* mailbox domain name (EXPERIMENTAL) */ const DnsMG : rectype = 8 /* mail group member (EXPERIMENTAL) */ const DnsMR : rectype = 9 /* mail rename domain name (EXPERIMENTAL) */ const DnsNULL : rectype = 10 /* null RR (EXPERIMENTAL) */ const DnsWKS : rectype = 11 /* well known service description */ const DnsPTR : rectype = 12 /* domain name pointer */ const DnsHINFO : rectype = 13 /* host information */ const DnsMINFO : rectype = 14 /* mailbox or mail list information */ const DnsMX : rectype = 15 /* mail exchange */ const DnsTXT : rectype = 16 /* text strings */ const DnsAAAA : rectype = 28 /* ipv6 host address */ type resolveerr = union `Badhost `Badsrv `Badquery `Badresp ;; type hostinfo = struct fam : sys.sockfam stype : sys.socktype ttl : uint32 addr : netaddr /* flags : uint32 addr : sockaddr[:] canon : byte[:] */ ;; 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" var hostmap : htab(byte[:], hostinfo)# var search : byte[:][:] var nameservers : netaddr[:] var inited : bool = false const resolve = {host -> resolverec(host, DnsA) } const resolvemx = {host -> resolverec(host, DnsMX) } const resolverec = {host, t match hostfind(host) | `Some hinf: -> `Ok slpush([][:], hinf) | `None: -> dnsresolve(host, DnsA) ;; } const hostfind = {host if !inited hostmap = mkht(strhash, streq) loadhosts() loadresolv() inited = true ;; -> htget(hostmap, host) } const loadhosts = { var h var lines match slurp(Hostfile) | `Ok d: h = d | `Fail m: -> ;; lines = strsplit(h, "\n") for l in 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) } 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): if hthas(hostmap, name) continue ;; hinf = [ .fam=fam, .stype = 0, .ttl = 0, .addr = addr ] htput(hostmap, name, hinf) str = rest | `None: -> ;; ;; } const loadresolv = { var h var lines match slurp(Resolvfile) | `Ok d: h = d | `Fail m: -> ;; lines = strsplit(h, "\n") for l in 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) } const addns = {rest match word(rest) | `Some (name, _): match ipparse(name) | `Some addr: nameservers = 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, t var nsrv if !valid(host) -> `Fail (`Badhost) ;; for ns in nameservers nsrv = dnsconnect(ns) if nsrv >= 0 -> dnsquery(nsrv, host, t) ;; ;; -> `Fail (`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 sa.port = hosttonet(53) sa.addr = addr status = sys.connect(s, (&sa) castto(sys.sockaddr#), sizeof(sys.sockaddr_in)) if status < 0 -> -1 ;; -> s } const dnsquery = {srv, host, t var id var r id = tquery(srv, host, t) r = rquery(srv, id) -> r } 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 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 castto(uint16)) /* qtype: a record */ off += pack16(pkt[:], off, 0x1) /* qclass: inet4 */ sys.write(srv, pkt[:off]) -> nextid++ } const rquery = {srv, id var pktbuf : byte[1024] var pkt var n n = sys.read(srv, pktbuf[:]) if n < 0 ;; pkt = pktbuf[:n] -> hosts(pkt, id) } const hosts = {pkt, id : uint16 var off var v, q, a var i var hinf : hostinfo[:] off = 0 /* parse header */ (v, off) = unpack16(pkt, off) /* id */ if v != id -> `Fail (`Badresp) ;; (v, off) = unpack16(pkt, off) /* flags */ (q, off) = unpack16(pkt, off) /* qdcount */ (a, off) = unpack16(pkt, off) /* ancount */ (v, off) = unpack16(pkt, off) /* nscount */ (v, off) = unpack16(pkt, off) /* arcount */ /* skip past query records */ for i = 0; i < q; i++ off = skipname(pkt, off) /* name */ (v, off) = unpack16(pkt, off) /* type */ (v, off) = unpack16(pkt, off) /* class */ ;; /* parse answer records */ hinf = slalloc(a castto(size)) for i = 0; i < a; i++ off = skipname(pkt, off) /* name */ (v, off) = unpack16(pkt, off) /* type */ (v, off) = unpack16(pkt, off) /* class */ (hinf[i].ttl, off) = unpack32(pkt, off) /* ttl */ (v, off) = unpack16(pkt, off) /* rdatalen */ /* the thing we're interested in: our IP address */ hinf[i].addr = `Ipv4 [pkt[off], pkt[off+1], pkt[off+2], pkt[off+3]] off += 4; ;; -> `Ok hinf } const skipname = {pkt, off var sz for sz = pkt[off] castto(size); sz != 0; sz = pkt[off] castto(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 castto(byte) buf[off+1] = (v & 0x00ff) castto(byte) -> sizeof(uint16) /* we always write one uint16 */ } const unpack16 = {buf, off var v v = (buf[off] castto(uint16)) << 8 v |= (buf[off + 1] castto(uint16)) -> (v, off+sizeof(uint16)) } const unpack32 = {buf, off var v v = (buf[off] castto(uint32)) << 24 v |= (buf[off+1] castto(uint32)) << 32 v |= (buf[off+2] castto(uint32)) << 8 v |= (buf[off+3] castto(uint32)) -> (v, off+sizeof(uint32)) } const packname = {buf, off : size, host var i var start var last start = off last = 0 for i = 0; i < host.len; i++ if host[i] == ('.' castto(byte)) off += addseg(buf, off, host[last:i]) last = i + 1 ;; ;; if host[host.len - 1] != ('.' castto(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 castto(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] == ('.' castto(byte)) seglen = 0 ;; if seglen > 63 -> false ;; if host[i] & 0x80 != 0 -> false ;; ;; -> true }