Skip to content

Commit

Permalink
feat: add support for query flags
Browse files Browse the repository at this point in the history
  • Loading branch information
mr-karan committed Jul 1, 2024
1 parent f1959e1 commit 2e5cd01
Show file tree
Hide file tree
Showing 11 changed files with 103 additions and 30 deletions.
39 changes: 30 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
```
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
```

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down
6 changes: 5 additions & 1 deletion cmd/api/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
23 changes: 20 additions & 3 deletions cmd/doggo/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
10 changes: 10 additions & 0 deletions cmd/doggo/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions pkg/resolvers/classic.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions pkg/resolvers/dnscrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
4 changes: 2 additions & 2 deletions pkg/resolvers/doh.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
10 changes: 5 additions & 5 deletions pkg/resolvers/doq.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion pkg/resolvers/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 23 additions & 2 deletions pkg/resolvers/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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,
Expand Down

0 comments on commit 2e5cd01

Please sign in to comment.