aboutsummaryrefslogtreecommitdiff
path: root/cmd/resolver
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/resolver')
-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())
+}