From 6ead9209bbc431b33ccbdebfffe98ffd23e4f9a7 Mon Sep 17 00:00:00 2001 From: Shulhan Date: Thu, 14 Apr 2022 01:33:40 +0700 Subject: cmd/resolver: refactor the resolver as client of DNS and rescached Previously, the resolver command only for querying DNS server. In this changes and in the future, the resolver command will be client for DNS and rescached server. --- cmd/resolver/main.go | 244 ++++++++++++++++----------------------------------- 1 file changed, 75 insertions(+), 169 deletions(-) (limited to 'cmd/resolver/main.go') diff --git a/cmd/resolver/main.go b/cmd/resolver/main.go index 2fdfe3b..49ba6a6 100644 --- a/cmd/resolver/main.go +++ b/cmd/resolver/main.go @@ -1,217 +1,123 @@ // SPDX-FileCopyrightText: 2018 M. Shulhan // SPDX-License-Identifier: GPL-3.0-or-later +// Command resolver is client for DNS server to resolve query and client for +// rescached HTTP server. package main import ( + "flag" "fmt" "log" - "math/rand" + "os" "strings" - "time" - - "github.com/shuLhan/share/lib/dns" - libnet "github.com/shuLhan/share/lib/net" ) +// List of valid commands. const ( - defResolvConf = "/etc/resolv.conf" + cmdQuery = "query" ) -// -// initSystemResolver read the system resolv.conf to create fallback DNS -// resolver. -// -func initSystemResolver() (rc *libnet.ResolvConf, cl dns.Client) { +func main() { var ( - err error - ns string + rsol = new(resolver) + + args []string + optHelp bool ) - rc, err = libnet.NewResolvConf(defResolvConf) - if err != nil { - log.Fatal("! ", err) - } + log.SetFlags(0) - if len(rc.NameServers) == 0 { - ns = "127.0.0.1:53" - } else { - ns = rc.NameServers[0] - } + flag.StringVar(&rsol.nameserver, "ns", "", "Parent name server address using scheme based.") + flag.BoolVar(&rsol.insecure, "insecure", false, "Ignore invalid server certificate") + flag.BoolVar(&optHelp, "h", false, "") - cl, err = dns.NewUDPClient(ns) - if err != nil { - log.Fatal("! ", err) - } + flag.Parse() - return -} + args = flag.Args() -func populateQueries(cr *libnet.ResolvConf, qname string) (queries []string) { - ndots := 0 + if optHelp { + help() + os.Exit(1) + } - for _, c := range qname { - if c == '.' { - ndots++ - continue - } + if len(args) == 0 { + help() + os.Exit(1) } - if ndots >= cr.NDots { - queries = append(queries, qname) - } else { - if len(cr.Domain) > 0 { - queries = append(queries, qname+"."+cr.Domain) - } - for _, s := range cr.Search { - queries = append(queries, qname+"."+s) + rsol.cmd = strings.ToLower(args[0]) + + switch rsol.cmd { + case cmdQuery: + args = args[1:] + if len(args) == 0 { + log.Fatalf("resolver: %s: missing argument", rsol.cmd) } - } - return -} + rsol.doCmdQuery(args) -func messagePrint(nameserver string, msg *dns.Message) string { - var b strings.Builder - - fmt.Fprintf(&b, "< From: %s", nameserver) - fmt.Fprintf(&b, "\n> Header: %+v", msg.Header) - fmt.Fprintf(&b, "\n> Question: %s", msg.Question.String()) - - b.WriteString("\n> Status:") - switch msg.Header.RCode { - case dns.RCodeOK: - b.WriteString(" OK") - case dns.RCodeErrFormat: - b.WriteString(" Invalid request format") - case dns.RCodeErrServer: - b.WriteString(" Server internal failure") - case dns.RCodeErrName: - b.WriteString(" Domain name did not exist") - case dns.RCodeNotImplemented: - b.WriteString(" Unknown query") - case dns.RCodeRefused: - b.WriteString(" Server refused the request") + default: + log.Printf("resolver: unknown command: %s", rsol.cmd) + os.Exit(2) } +} - if msg.Header.RCode != dns.RCodeOK { - return b.String() - } +func help() { + fmt.Println(` += resolver: command line interface for DNS and rescached server - for x, rr := range msg.Answer { - fmt.Fprintf(&b, "\n> Answer #%d:", x+1) - fmt.Fprintf(&b, "\n>> Resource record: %s", rr.String()) - fmt.Fprintf(&b, "\n>> RDATA: %s", rr.Value) - } - for x, rr := range msg.Authority { - fmt.Fprintf(&b, "\n> Authority #%d:", x+1) - fmt.Fprintf(&b, "\n>> Resource record: %s", rr.String()) - fmt.Fprintf(&b, "\n>> RDATA: %s", rr.Value) - } - for x, rr := range msg.Additional { - fmt.Fprintf(&b, "\n> Additional #%d:", x+1) - fmt.Fprintf(&b, "\n>> Resource record: %s", rr.String()) - fmt.Fprintf(&b, "\n>> RDATA: %s", rr.Value) - } +== Usage - return b.String() -} + resolver [-ns nameserver] [-insecure] -func lookup(opts *options, cl dns.Client, timeout time.Duration, qname string, -) *dns.Message { - var ( - err error - ) +== Options - rand.Seed(time.Now().Unix()) +Accepted command is query. - cl.SetTimeout(timeout) +-ns nameserver - req := dns.NewMessage() - req.Header.ID = uint16(rand.Intn(65535)) - req.Question.Name = qname - req.Question.Type = opts.qtype - req.Question.Class = opts.qclass - _, err = req.Pack() - if err != nil { - log.Fatal("! Pack:", err) - } + Parent name server address using scheme based. + For example, + udp://35.240.172.103:53 for querying with UDP, + tcp://35.240.172.103:53 for querying with TCP, + https://35.240.172:103:853 for querying with DNS over TLS (DoT), and + https://kilabit.info/dns-query for querying with DNS over HTTPS (DoH). - res, err := cl.Query(req) - if err != nil { - log.Println("! Lookup: ", err) - return nil - } +-insecure - if res.Header.RCode == 0 { - return res - } + Ignore invalid server certificate when querying DoT, DoH, or rescached + server. - switch res.Header.RCode { - case dns.RCodeErrFormat: - log.Println("! ResponseCode: Format error") - case dns.RCodeErrServer: - log.Println("! ResponseCode: Server failure") - case dns.RCodeErrName: - log.Println("! ResponseCode: Domain not exist") - case dns.RCodeNotImplemented: - log.Println("! ResponseCode: Not implemented") - case dns.RCodeRefused: - log.Println("! ResponseCode: Refused") - } - return nil -} +== Commands -func main() { - var ( - cl dns.Client - rc *libnet.ResolvConf - res *dns.Message - err error - ) +query [type] [class] - log.SetFlags(0) + Query the domain or IP address with optional type and/or class. - opts, err := newOptions() - if err != nil { - log.Fatal("! ", err) - } + Unless the option "-ns" is given, the query command will use the + nameserver defined in the system resolv.conf file. - fmt.Printf("= options: %+v\n", opts) + Valid type are either A, NS, CNAME, SOA, MB, MG, MR, NULL, + WKS, PTR, HINFO, MINFO, MX, TXT, AAAA, or SRV. + Default value is A." - rc, systemResolver := initSystemResolver() + Valid class are either IN, CS, HS. + Default value is IN. - fmt.Printf("= resolv.conf: %+v\n", rc) +== Examples - if len(opts.nameserver) == 0 { - cl = systemResolver - } else { - cl, err = dns.NewClient(opts.nameserver, opts.insecure) - if err != nil { - log.Fatal(err) - } - } +Query the MX records using UDP on name server 35.240.172.103, - queries := populateQueries(rc, opts.qname) - timeout := time.Duration(rc.Timeout) * time.Second - - // The algorithm used is to try a name server, and if the query - // times out, try the next, until out of name servers, then repeat - // trying all the name servers until a maximum number of retries are - // made.) - for _, qname := range queries { - for x := 0; x < rc.Attempts; x++ { - fmt.Printf("> Lookup %s at %s\n", qname, cl.RemoteAddr()) - - res = lookup(opts, cl, timeout, qname) - if res != nil { - goto out - } - } - } + $ resolver -ns udp://35.240.172.103 query kilabit.info MX -out: - if res != nil { - println(messagePrint(cl.RemoteAddr(), res)) - } +Query the IPv4 records of domain name "kilabit.info" using DNS over TLS on +name server 35.240.172.103, + + $ resolver -ns https://35.240.172.103 -insecure query kilabit.info + +Query the IPv4 records of domain name "kilabit.info" using DNS over HTTPS on +name server kilabit.info, + + $ resolver -ns https://kilabit.info/dns-query query kilabit.info`) } -- cgit v1.3