From 2e5cd01329bdbf568e5037f7fbd6137ad29ceb08 Mon Sep 17 00:00:00 2001 From: Karan Sharma Date: Fri, 28 Jun 2024 15:45:38 +0530 Subject: [PATCH] feat: add support for query flags --- README.md | 39 ++++++++++++++++++++++++++++++--------- TODO.md | 4 ++-- cmd/api/handlers.go | 6 +++++- cmd/doggo/cli.go | 23 ++++++++++++++++++++--- cmd/doggo/help.go | 10 ++++++++++ pkg/resolvers/classic.go | 6 +++--- pkg/resolvers/dnscrypt.go | 4 ++-- pkg/resolvers/doh.go | 4 ++-- pkg/resolvers/doq.go | 10 +++++----- pkg/resolvers/resolver.go | 2 +- pkg/resolvers/utils.go | 25 +++++++++++++++++++++++-- 11 files changed, 103 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 02a5067..e5a81fa 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ to experiment with writing a DNS Client from scratch in `Go` myself. Hence the n - Available as a web tool as well: [https://doggo.mrkaran.dev](https://doggo.mrkaran.dev). - Shell completions for `zsh` and `fish`. - Reverse DNS Lookups. +- Supports multiple query flags. ## Installation @@ -104,8 +105,8 @@ The binary will be available at `$GOPATH/bin/doggo`. **Do a simple DNS Lookup for `mrkaran.dev`** ```bash -$ doggo mrkaran.dev -NAME TYPE CLASS TTL ADDRESS NAMESERVER +$ doggo mrkaran.dev +NAME TYPE CLASS TTL ADDRESS NAMESERVER mrkaran.dev. A IN 20s 13.250.205.9 127.0.0.1:53 mrkaran.dev. A IN 20s 206.189.89.118 127.0.0.1:53 ``` @@ -114,7 +115,7 @@ mrkaran.dev. A IN 20s 206.189.89.118 127.0.0.1:53 ``` doggo MX github.com @9.9.9.9 -NAME TYPE CLASS TTL ADDRESS NAMESERVER +NAME TYPE CLASS TTL ADDRESS NAMESERVER github.com. MX IN 3600s 10 alt3.aspmx.l.google.com. 9.9.9.9:53 github.com. MX IN 3600s 5 alt1.aspmx.l.google.com. 9.9.9.9:53 github.com. MX IN 3600s 10 alt4.aspmx.l.google.com. 9.9.9.9:53 @@ -126,7 +127,7 @@ or using _named parameters_: ```bash $ doggo -t MX -n 9.9.9.9 github.com -NAME TYPE CLASS TTL ADDRESS NAMESERVER +NAME TYPE CLASS TTL ADDRESS NAMESERVER github.com. MX IN 3600s 10 alt3.aspmx.l.google.com. 9.9.9.9:53 github.com. MX IN 3600s 5 alt1.aspmx.l.google.com. 9.9.9.9:53 github.com. MX IN 3600s 10 alt4.aspmx.l.google.com. 9.9.9.9:53 @@ -137,8 +138,8 @@ github.com. MX IN 3600s 1 aspmx.l.google.com. 9.9.9.9: **Query DNS records for `archive.org` using Cloudflare DoH resolver** ```bash -$ doggo archive.org @https://cloudflare-dns.com/dns-query -NAME TYPE CLASS TTL ADDRESS NAMESERVER +$ doggo archive.org @https://cloudflare-dns.com/dns-query +NAME TYPE CLASS TTL ADDRESS NAMESERVER archive.org. A IN 41s 207.241.224.2 https://cloudflare-dns.com/dns-query ``` @@ -191,9 +192,9 @@ $ doggo internetfreedom.in --json | jq **Query DNS records for `duckduckgo.com` and show RTT (Round Trip Time)** ```bash -$ doggo duckduckgo.com --time -NAME TYPE CLASS TTL ADDRESS NAMESERVER TIME TAKEN -duckduckgo.com. A IN 30s 40.81.94.43 127.0.0.1:53 45ms +$ doggo duckduckgo.com --time +NAME TYPE CLASS TTL ADDRESS NAMESERVER TIME TAKEN +duckduckgo.com. A IN 30s 40.81.94.43 127.0.0.1:53 45ms ``` ## Command-line Arguments @@ -248,6 +249,26 @@ URL scheme of the server is used to identify which resolver to use for lookups. --short Short output format. Shows only the response section. ``` +### Query Flags + +``` + --aa Set Authoritative Answer flag. + --ad Set Authenticated Data flag. + --cd Set Checking Disabled flag. + --rd Set Recursion Desired flag (default: true). + --z Set Z flag (reserved for future use). + --do Set DNSSEC OK flag. +``` + +These flags allow you to control various aspects of the DNS query: + +- `--aa`: Request an authoritative answer from the server. +- `--ad`: Request authenticated data in the response. +- `--cd`: Disable DNSSEC validation. +- `--rd`: Enable recursive querying (enabled by default). +- `--z`: Set the Z bit, which is reserved for future use. +- `--do`: Set the DNSSEC OK bit to request DNSSEC records. + --- ## Contributing diff --git a/TODO.md b/TODO.md index 30cca2f..7ee4d79 100644 --- a/TODO.md +++ b/TODO.md @@ -13,7 +13,7 @@ - [x] Change lookup method. - [x] Major records supported - [x] Support multiple resolvers - - [x] Take multiple transport options and initialise resolvers accordingly. + - [x] Take multiple transport options and initialise resolvers accordingly. - [x] Add timeout support - [x] Support SOA/NXDOMAIN @@ -60,7 +60,7 @@ - [x] fish - [ ] Add tests for Resolvers. - [ ] Add tests for CLI Output. -- [ ] Homebrew - Goreleaser +- [x] Homebrew - Goreleaser - [ ] Add support for `dig +trace` like functionality. - [ ] Add `dig +short` short output - [x] Add `--strategy` for picking nameservers. diff --git a/cmd/api/handlers.go b/cmd/api/handlers.go index 956b470..3d561e3 100644 --- a/cmd/api/handlers.go +++ b/cmd/api/handlers.go @@ -94,10 +94,14 @@ func handleLookup(w http.ResponseWriter, r *http.Request) { app.Logger.WithField("resolvers", app.Resolvers).Debug("Loaded resolvers") + queryFlags := resolvers.QueryFlags{ + RD: true, + } + var responses []resolvers.Response for _, q := range app.Questions { for _, rslv := range app.Resolvers { - resp, err := rslv.Lookup(q) + resp, err := rslv.Lookup(q, queryFlags) if err != nil { app.Logger.WithError(err).Error("error looking up DNS records") sendErrorResponse(w, fmt.Sprintf("Error looking up for records."), http.StatusInternalServerError, nil) diff --git a/cmd/doggo/cli.go b/cmd/doggo/cli.go index 5c3f2c3..5c8070c 100644 --- a/cmd/doggo/cli.go +++ b/cmd/doggo/cli.go @@ -81,7 +81,16 @@ func main() { app.Logger.Exit(0) } - responses, responseErrors := resolveQueries(&app) + queryFlags := resolvers.QueryFlags{ + AA: k.Bool("aa"), + AD: k.Bool("ad"), + CD: k.Bool("cd"), + RD: k.Bool("rd"), + Z: k.Bool("z"), + DO: k.Bool("do"), + } + + responses, responseErrors := resolveQueries(&app, queryFlags) outputResults(&app, responses, responseErrors) @@ -113,6 +122,14 @@ func setupFlags() *flag.FlagSet { f.Bool("color", true, "Show colored output") f.Bool("debug", false, "Enable debug mode") + // Add flags for DNS query options + f.Bool("aa", false, "Set Authoritative Answer flag") + f.Bool("ad", false, "Set Authenticated Data flag") + f.Bool("cd", false, "Set Checking Disabled flag") + f.Bool("rd", true, "Set Recursion Desired flag (default: true)") + f.Bool("z", false, "Set Z flag (reserved for future use)") + f.Bool("do", false, "Set DNSSEC OK flag") + f.Bool("version", false, "Show version of doggo") return f @@ -156,13 +173,13 @@ func loadNameservers(app *app.App, args []string) { app.Logger.WithField("finalNameservers", app.QueryFlags.Nameservers).Debug("Final nameservers") } -func resolveQueries(app *app.App) ([]resolvers.Response, []error) { +func resolveQueries(app *app.App, flags resolvers.QueryFlags) ([]resolvers.Response, []error) { var responses []resolvers.Response var responseErrors []error for _, q := range app.Questions { for _, rslv := range app.Resolvers { - resp, err := rslv.Lookup(q) + resp, err := rslv.Lookup(q, flags) if err != nil { responseErrors = append(responseErrors, err) } diff --git a/cmd/doggo/help.go b/cmd/doggo/help.go index 98a6d2d..8e89cc4 100644 --- a/cmd/doggo/help.go +++ b/cmd/doggo/help.go @@ -23,6 +23,8 @@ var appHelpTextTemplate = `{{ "NAME" | color "" "heading" }}: {{ .Name | color "green" "bold" }} {{ "mrkaran.dev CNAME" | color "cyan" "" }} Query for a CNAME record. {{ .Name | color "green" "bold" }} {{ "mrkaran.dev MX @9.9.9.9" | color "cyan" "" }} Uses a custom DNS resolver. {{ .Name | color "green" "bold" }} {{"-q mrkaran.dev -t MX -n 1.1.1.1" | color "yellow" ""}} Using named arguments. + {{ .Name | color "green" "bold" }} {{ "mrkaran.dev --aa --ad" | color "cyan" "" }} Query with Authoritative Answer and Authenticated Data flags set. + {{ .Name | color "green" "bold" }} {{ "mrkaran.dev --cd --do" | color "cyan" "" }} Query with Checking Disabled and DNSSEC OK flags set. {{ "Free Form Arguments" | color "" "heading" }}: Supply hostnames, query types, and classes without flags. Example: @@ -57,6 +59,14 @@ var appHelpTextTemplate = `{{ "NAME" | color "" "heading" }}: {{"--tls-hostname=HOSTNAME" | color "yellow" ""}} Provide a hostname for doing verification of the certificate if the provided DoT nameserver is an IP. {{"--skip-hostname-verification" | color "yellow" ""}} Skip TLS Hostname Verification in case of DOT Lookups. +{{ "Query Flags" | color "" "heading" }}: + {{"--aa" | color "yellow" ""}} Set Authoritative Answer flag. + {{"--ad" | color "yellow" ""}} Set Authenticated Data flag. + {{"--cd" | color "yellow" ""}} Set Checking Disabled flag. + {{"--rd" | color "yellow" ""}} Set Recursion Desired flag (default: true). + {{"--z" | color "yellow" ""}} Set Z flag (reserved for future use). + {{"--do" | color "yellow" ""}} Set DNSSEC OK flag. + {{ "Output Options" | color "" "heading" }}: {{"-J, --json " | color "yellow" ""}} Format the output as JSON. {{"--short" | color "yellow" ""}} Short output format. Shows only the response section. diff --git a/pkg/resolvers/classic.go b/pkg/resolvers/classic.go index 366e1d0..7101b96 100644 --- a/pkg/resolvers/classic.go +++ b/pkg/resolvers/classic.go @@ -60,10 +60,10 @@ func NewClassicResolver(server string, classicOpts ClassicResolverOpts, resolver // Lookup takes a dns.Question and sends them to DNS Server. // It parses the Response from the server in a custom output format. -func (r *ClassicResolver) Lookup(question dns.Question) (Response, error) { +func (r *ClassicResolver) Lookup(question dns.Question, flags QueryFlags) (Response, error) { var ( rsp Response - messages = prepareMessages(question, r.resolverOptions.Ndots, r.resolverOptions.SearchList) + messages = prepareMessages(question, flags, r.resolverOptions.Ndots, r.resolverOptions.SearchList) ) for _, msg := range messages { r.resolverOptions.Logger.WithFields(logrus.Fields{ @@ -94,7 +94,7 @@ func (r *ClassicResolver) Lookup(question dns.Question) (Response, error) { r.client.Net = "tcp" } r.resolverOptions.Logger.WithField("protocol", r.client.Net).Debug("Response truncated; retrying now") - return r.Lookup(question) + return r.Lookup(question, flags) } // Pack questions in output. diff --git a/pkg/resolvers/dnscrypt.go b/pkg/resolvers/dnscrypt.go index 2239379..49359ad 100644 --- a/pkg/resolvers/dnscrypt.go +++ b/pkg/resolvers/dnscrypt.go @@ -43,10 +43,10 @@ func NewDNSCryptResolver(server string, dnscryptOpts DNSCryptResolverOpts, resol // Lookup takes a dns.Question and sends them to DNS Server. // It parses the Response from the server in a custom output format. -func (r *DNSCryptResolver) Lookup(question dns.Question) (Response, error) { +func (r *DNSCryptResolver) Lookup(question dns.Question, flags QueryFlags) (Response, error) { var ( rsp Response - messages = prepareMessages(question, r.resolverOptions.Ndots, r.resolverOptions.SearchList) + messages = prepareMessages(question, flags, r.resolverOptions.Ndots, r.resolverOptions.SearchList) ) for _, msg := range messages { r.resolverOptions.Logger.WithFields(logrus.Fields{ diff --git a/pkg/resolvers/doh.go b/pkg/resolvers/doh.go index c0f114a..a028460 100644 --- a/pkg/resolvers/doh.go +++ b/pkg/resolvers/doh.go @@ -49,10 +49,10 @@ func NewDOHResolver(server string, resolverOpts Options) (Resolver, error) { // Lookup takes a dns.Question and sends them to DNS Server. // It parses the Response from the server in a custom output format. -func (r *DOHResolver) Lookup(question dns.Question) (Response, error) { +func (r *DOHResolver) Lookup(question dns.Question, flags QueryFlags) (Response, error) { var ( rsp Response - messages = prepareMessages(question, r.resolverOptions.Ndots, r.resolverOptions.SearchList) + messages = prepareMessages(question, flags, r.resolverOptions.Ndots, r.resolverOptions.SearchList) ) for _, msg := range messages { diff --git a/pkg/resolvers/doq.go b/pkg/resolvers/doq.go index e5492ab..7cd5358 100644 --- a/pkg/resolvers/doq.go +++ b/pkg/resolvers/doq.go @@ -26,9 +26,9 @@ type DOQResolver struct { func NewDOQResolver(server string, resolverOpts Options) (Resolver, error) { return &DOQResolver{ tls: &tls.Config{ - NextProtos: []string{"doq"}, - ServerName: resolverOpts.TLSHostname, - InsecureSkipVerify: resolverOpts.InsecureSkipVerify, + NextProtos: []string{"doq"}, + ServerName: resolverOpts.TLSHostname, + InsecureSkipVerify: resolverOpts.InsecureSkipVerify, }, server: server, resolverOptions: resolverOpts, @@ -37,10 +37,10 @@ func NewDOQResolver(server string, resolverOpts Options) (Resolver, error) { // Lookup takes a dns.Question and sends them to DNS Server. // It parses the Response from the server in a custom output format. -func (r *DOQResolver) Lookup(question dns.Question) (Response, error) { +func (r *DOQResolver) Lookup(question dns.Question, flags QueryFlags) (Response, error) { var ( rsp Response - messages = prepareMessages(question, r.resolverOptions.Ndots, r.resolverOptions.SearchList) + messages = prepareMessages(question, flags, r.resolverOptions.Ndots, r.resolverOptions.SearchList) ) session, err := quic.DialAddr(context.TODO(), r.server, r.tls, nil) diff --git a/pkg/resolvers/resolver.go b/pkg/resolvers/resolver.go index cccbf42..1e0f920 100644 --- a/pkg/resolvers/resolver.go +++ b/pkg/resolvers/resolver.go @@ -28,7 +28,7 @@ type Options struct { // Client. Different types of providers can load // a DNS Resolver satisfying this interface. type Resolver interface { - Lookup(dns.Question) (Response, error) + Lookup(dns.Question, QueryFlags) (Response, error) } // Response represents a custom output format diff --git a/pkg/resolvers/utils.go b/pkg/resolvers/utils.go index 4aded7d..d73bd95 100644 --- a/pkg/resolvers/utils.go +++ b/pkg/resolvers/utils.go @@ -9,9 +9,19 @@ import ( "github.com/miekg/dns" ) +// QueryFlags represents the various DNS query flags +type QueryFlags struct { + AA bool // Authoritative Answer + AD bool // Authenticated Data + CD bool // Checking Disabled + RD bool // Recursion Desired + Z bool // Reserved for future use + DO bool // DNSSEC OK +} + // prepareMessages takes a DNS Question and returns the // corresponding DNS messages for the same. -func prepareMessages(q dns.Question, ndots int, searchList []string) []dns.Msg { +func prepareMessages(q dns.Question, flags QueryFlags, ndots int, searchList []string) []dns.Msg { var ( possibleQNames = constructPossibleQuestions(q.Name, ndots, searchList) messages = make([]dns.Msg, 0, len(possibleQNames)) @@ -21,7 +31,18 @@ func prepareMessages(q dns.Question, ndots int, searchList []string) []dns.Msg { msg := dns.Msg{} // generate a random id for the transaction. msg.Id = dns.Id() - msg.RecursionDesired = true + + // Set query flags + msg.RecursionDesired = flags.RD + msg.AuthenticatedData = flags.AD + msg.CheckingDisabled = flags.CD + msg.Authoritative = flags.AA + msg.Zero = flags.Z + + if flags.DO { + msg.SetEdns0(4096, flags.DO) + } + // It's recommended to only send 1 question for 1 DNS message. msg.Question = []dns.Question{{ Name: qName,