summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2022-04-14 01:33:40 +0700
committerShulhan <ms@kilabit.info>2022-04-14 01:33:40 +0700
commit6ead9209bbc431b33ccbdebfffe98ffd23e4f9a7 (patch)
treefbf3cebda822e168bab1f6ee8623295be43da532
parentbb7ec2c3667a45f9a534b3f568cb5148f828a1a9 (diff)
downloadrescached-6ead9209bbc431b33ccbdebfffe98ffd23e4f9a7.tar.xz
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.
-rw-r--r--cmd/resolver/main.go238
-rw-r--r--cmd/resolver/options.go162
-rw-r--r--cmd/resolver/resolver.go243
3 files changed, 315 insertions, 328 deletions
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 <ms@kilabit.info>
// 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)
- rc, err = libnet.NewResolvConf(defResolvConf)
- if err != nil {
- log.Fatal("! ", err)
- }
+ args []string
+ optHelp bool
+ )
- if len(rc.NameServers) == 0 {
- ns = "127.0.0.1:53"
- } else {
- ns = rc.NameServers[0]
- }
+ log.SetFlags(0)
- cl, err = dns.NewUDPClient(ns)
- if err != nil {
- log.Fatal("! ", err)
- }
+ 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, "")
- return
-}
+ flag.Parse()
-func populateQueries(cr *libnet.ResolvConf, qname string) (queries []string) {
- ndots := 0
+ args = flag.Args()
- for _, c := range qname {
- if c == '.' {
- ndots++
- continue
- }
+ if optHelp {
+ 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)
- }
+ if len(args) == 0 {
+ help()
+ os.Exit(1)
}
- return
-}
+ rsol.cmd = strings.ToLower(args[0])
-func messagePrint(nameserver string, msg *dns.Message) string {
- var b strings.Builder
+ switch rsol.cmd {
+ case cmdQuery:
+ args = args[1:]
+ if len(args) == 0 {
+ log.Fatalf("resolver: %s: missing argument", rsol.cmd)
+ }
- fmt.Fprintf(&b, "< From: %s", nameserver)
- fmt.Fprintf(&b, "\n> Header: %+v", msg.Header)
- fmt.Fprintf(&b, "\n> Question: %s", msg.Question.String())
+ rsol.doCmdQuery(args)
- 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] <command> <args>
-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 <domain / ip-address> [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
+ $ resolver -ns udp://35.240.172.103 query kilabit.info MX
- // 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())
+Query the IPv4 records of domain name "kilabit.info" using DNS over TLS on
+name server 35.240.172.103,
- res = lookup(opts, cl, timeout, qname)
- if res != nil {
- goto out
- }
- }
- }
+ $ resolver -ns https://35.240.172.103 -insecure query kilabit.info
-out:
- if res != nil {
- println(messagePrint(cl.RemoteAddr(), res))
- }
+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`)
}
diff --git a/cmd/resolver/options.go b/cmd/resolver/options.go
deleted file mode 100644
index 95d9aeb..0000000
--- a/cmd/resolver/options.go
+++ /dev/null
@@ -1,162 +0,0 @@
-// SPDX-FileCopyrightText: 2018 M. Shulhan <ms@kilabit.info>
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-package main
-
-import (
- "errors"
- "flag"
- "fmt"
- "os"
- "strings"
-
- "github.com/shuLhan/share/lib/dns"
-)
-
-// List of error messages.
-var (
- errQueryName = errors.New("invalid or empty query name")
- errRecordType = errors.New("unknown query type")
- errRecordClass = errors.New("unknown query class")
-)
-
-// List of command line usages.
-const (
- usageInsecure = `Skip verifying server certificate`
- usageNameServer = "Parent name server address using scheme based.\n" +
- "\tFor example,\n" +
- "\tudp://35.240.172.103:53 for querying with UDP,\n" +
- "\ttcp://35.240.172.103:53 for querying with TCP,\n" +
- "\thttps://35.240.172:103:853 for querying with DNS over TLS, and\n" +
- "\thttps://kilabit.info/dns-query for querying with DNS over HTTPS."
-
- usageType = "Query type. Valid values are either A, NS, CNAME, SOA,\n" +
- "\tMB, MG, MR, NULL, WKS, PTR, HINFO, MINFO, MX, TXT, AAAA, or SRV.\n" +
- "\tDefault value is A."
-
- usageClass = "Query class. Valid values are either IN, CS, HS.\n" +
- "\tDefault value is IN."
-)
-
-type options struct {
- sqtype string
- sqclass string
-
- nameserver string
- qname string
- qtype dns.RecordType
- qclass dns.RecordClass
-
- insecure bool
-}
-
-func help() {
- fmt.Println(`
-= resolver: command line interface for DNS query
-
-== Usage
-
- resolver [-ns nameserver] [-insecure] [-t string] [-c string] [domain|address]
-
-== Options
-
--ns nameserver
-
- ` + usageNameServer + `
-
--insecure
-
- ` + usageInsecure + `
-
--t string
-
- ` + usageType + `
-
--c string
-
- ` + usageClass + `
-
-== Examples
-
-Query the MX records using UDP on name server 35.240.172.103,
-
- $ resolver -ns udp://35.240.172.103 -t MX kilabit.info
-
-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 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 kilabit.info`)
-}
-
-func newOptions() (*options, error) {
- var optHelp bool
-
- opts := new(options)
-
- flag.StringVar(&opts.nameserver, "ns", "", usageNameServer)
- flag.BoolVar(&opts.insecure, "insecure", false, usageInsecure)
- flag.BoolVar(&optHelp, "h", false, "")
- flag.StringVar(&opts.sqtype, "t", "A", usageType)
- flag.StringVar(&opts.sqclass, "c", "IN", usageClass)
-
- flag.Parse()
-
- args := flag.Args()
-
- if optHelp {
- help()
- os.Exit(1)
- }
-
- if len(args) == 0 {
- help()
- os.Exit(1)
- }
-
- opts.qname = args[0]
-
- err := opts.parseQType()
- if err != nil {
- help()
- os.Exit(1)
- }
-
- err = opts.parseQClass()
- if err != nil {
- help()
- os.Exit(1)
- }
-
- return opts, nil
-}
-
-func (opts *options) parseQType() error {
- var ok bool
-
- opts.sqtype = strings.ToUpper(opts.sqtype)
-
- opts.qtype, ok = dns.RecordTypes[opts.sqtype]
- if !ok {
- return errRecordType
- }
-
- return nil
-}
-
-func (opts *options) parseQClass() error {
- var ok bool
-
- opts.sqclass = strings.ToUpper(opts.sqclass)
-
- opts.qclass, ok = dns.RecordClasses[opts.sqclass]
- if !ok {
- return errRecordClass
- }
-
- return nil
-}
diff --git a/cmd/resolver/resolver.go b/cmd/resolver/resolver.go
new file mode 100644
index 0000000..19cebaf
--- /dev/null
+++ b/cmd/resolver/resolver.go
@@ -0,0 +1,243 @@
+// SPDX-FileCopyrightText: 2018 M. Shulhan <ms@kilabit.info>
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package main
+
+import (
+ "fmt"
+ "log"
+ "math/rand"
+ "strings"
+ "time"
+
+ "github.com/shuLhan/share/lib/dns"
+ libnet "github.com/shuLhan/share/lib/net"
+)
+
+const (
+ defAttempts = 1
+ defQueryType = "A"
+ defQueryClass = "IN"
+ defResolvConf = "/etc/resolv.conf"
+ defTimeout = 5 * time.Second
+)
+
+type resolver struct {
+ conf *libnet.ResolvConf
+ dnsc dns.Client
+
+ cmd string
+ qname string
+ sqtype string
+ sqclass string
+
+ nameserver string
+ qtype dns.RecordType
+ qclass dns.RecordClass
+
+ insecure bool
+}
+
+func (rsol *resolver) doCmdQuery(args []string) {
+ var (
+ maxAttempts = defAttempts
+ timeout = defTimeout
+
+ res *dns.Message
+ qname string
+ queries []string
+ nAttempts int
+ err error
+ ok bool
+ )
+
+ rsol.qname = args[0]
+
+ switch len(args) {
+ case 1:
+ rsol.sqtype = defQueryType
+ rsol.sqclass = defQueryClass
+
+ case 2:
+ rsol.sqtype = args[1]
+ rsol.sqclass = defQueryClass
+
+ case 3:
+ rsol.sqtype = args[1]
+ rsol.sqclass = args[2]
+ }
+
+ rsol.sqtype = strings.ToUpper(rsol.sqtype)
+ rsol.qtype, ok = dns.RecordTypes[rsol.sqtype]
+ if !ok {
+ log.Fatalf("resolver: invalid query type: %q", rsol.sqtype)
+ }
+
+ rsol.sqclass = strings.ToUpper(rsol.sqclass)
+ rsol.qclass, ok = dns.RecordClasses[rsol.sqclass]
+ if !ok {
+ log.Fatalf("resolver: invalid query class: %q", rsol.sqclass)
+ }
+
+ fmt.Printf("= options: %+v\n", rsol)
+
+ if len(rsol.nameserver) == 0 {
+ // Use the nameserver and configuration from resolv.conf.
+ err = rsol.initSystemResolver()
+ if err != nil {
+ log.Fatalf("resolver: %s", err)
+ }
+
+ fmt.Printf("= resolv.conf: %+v\n", rsol.conf)
+
+ queries = populateQueries(rsol.conf, rsol.qname)
+ timeout = time.Duration(rsol.conf.Timeout) * time.Second
+ maxAttempts = rsol.conf.Attempts
+ } else {
+ rsol.dnsc, err = dns.NewClient(rsol.nameserver, rsol.insecure)
+ if err != nil {
+ log.Fatalf("resolver: %s", err)
+ }
+
+ queries = append(queries, rsol.qname)
+ }
+
+ for _, qname = range queries {
+ for nAttempts = 0; nAttempts < maxAttempts; nAttempts++ {
+ fmt.Printf("< Query %s at %s\n", qname, rsol.dnsc.RemoteAddr())
+
+ res, err = rsol.query(timeout, qname)
+ if err != nil {
+ log.Printf("resolver: %s", err)
+ continue
+ }
+
+ printQueryResponse(rsol.dnsc.RemoteAddr(), res)
+ return
+ }
+ }
+}
+
+//
+// initSystemResolver read the system resolv.conf to create fallback DNS
+// resolver.
+//
+func (rsol *resolver) initSystemResolver() (err error) {
+ var (
+ logp = "initSystemResolver"
+
+ ns string
+ )
+
+ rsol.conf, err = libnet.NewResolvConf(defResolvConf)
+ if err != nil {
+ return fmt.Errorf("%s: %w", logp, err)
+ }
+
+ if len(rsol.conf.NameServers) == 0 {
+ ns = "127.0.0.1:53"
+ } else {
+ ns = rsol.conf.NameServers[0]
+ }
+
+ rsol.dnsc, err = dns.NewUDPClient(ns)
+ if err != nil {
+ return fmt.Errorf("%s: %w", logp, err)
+ }
+ return nil
+}
+
+func (rsol *resolver) query(timeout time.Duration, qname string) (res *dns.Message, err error) {
+ var (
+ logp = "query"
+ req = dns.NewMessage()
+ )
+
+ rand.Seed(time.Now().Unix())
+
+ rsol.dnsc.SetTimeout(timeout)
+
+ req.Header.ID = uint16(rand.Intn(65535))
+ req.Question.Name = qname
+ req.Question.Type = rsol.qtype
+ req.Question.Class = rsol.qclass
+ _, err = req.Pack()
+ if err != nil {
+ return nil, fmt.Errorf("%s: %s: %w", logp, qname, err)
+ }
+
+ res, err = rsol.dnsc.Query(req)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %s: %w", logp, qname, err)
+ }
+
+ return res, nil
+}
+
+func populateQueries(cr *libnet.ResolvConf, qname string) (queries []string) {
+ ndots := 0
+
+ for _, c := range qname {
+ if c == '.' {
+ ndots++
+ continue
+ }
+ }
+
+ 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)
+ }
+ }
+
+ return
+}
+
+func printQueryResponse(nameserver string, msg *dns.Message) {
+ 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:
+ fmt.Fprintf(&b, "Domain name with type %s and class %s did not exist",
+ dns.RecordTypeNames[msg.Question.Type],
+ dns.RecordClassName[msg.Question.Class])
+ case dns.RCodeNotImplemented:
+ b.WriteString(" Unknown query")
+ case dns.RCodeRefused:
+ b.WriteString(" Server refused the request")
+ }
+
+ 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: %+v", 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: %+v", 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: %+v", rr.Value)
+ }
+
+ fmt.Println(b.String())
+}