diff --git a/TODO.md b/TODO.md index 8abd542..c14727a 100644 --- a/TODO.md +++ b/TODO.md @@ -62,5 +62,8 @@ - [ ] Add tests for CLI Output. - [ ] Homebrew - Goreleaser - [ ] Add support for `dig +trace` like functionality. +- [ ] Add `dig +x` short output +- [x] Add `--strategy` for picking nameservers. +- [ ] Explore `dig.rc` kinda file - [x] Separate Authority/Answer in JSON output. - [x] Error on NXDomain (Related upstream [bug](https://github.com/miekg/dns/issues/1198)) diff --git a/^ b/^ new file mode 100644 index 0000000..18b75d6 --- /dev/null +++ b/^ @@ -0,0 +1 @@ +nameserver 127.0.w0.1 diff --git a/cmd/doggo/cli.go b/cmd/doggo/cli.go index a413f18..9ca5afb 100644 --- a/cmd/doggo/cli.go +++ b/cmd/doggo/cli.go @@ -45,6 +45,7 @@ func main() { f.Int("ndots", -1, "Specify the ndots parameter. Default value is taken from resolv.conf and fallbacks to 1 if ndots statement is missing in resolv.conf") f.BoolP("ipv4", "4", false, "Use IPv4 only") f.BoolP("ipv6", "6", false, "Use IPv6 only") + f.String("strategy", "all", "Strategy to query nameservers in resolv.conf file (`all`, `random`, `first`)") // Output Options f.BoolP("json", "J", false, "Set the output format as JSON") @@ -126,6 +127,7 @@ func main() { Ndots: app.ResolverOpts.Ndots, Timeout: app.QueryFlags.Timeout * time.Second, Logger: app.Logger, + Strategy: app.QueryFlags.Strategy, }) if err != nil { app.Logger.WithError(err).Error("error loading resolver") diff --git a/cmd/doggo/help.go b/cmd/doggo/help.go index c594387..3b23ba4 100644 --- a/cmd/doggo/help.go +++ b/cmd/doggo/help.go @@ -47,11 +47,12 @@ var appHelpTextTemplate = `{{ "NAME" | color "" "heading" }}: {{"-x, --reverse" | color "yellow" ""}} Performs a DNS Lookup for an IPv4 or IPv6 address. Sets the query type and class to PTR and IN respectively. {{ "Resolver Options" | color "" "heading" }}: - {{"--ndots=INT" | color "yellow" ""}} Specify ndots parameter. Takes value from /etc/resolv.conf if using the system namesever or 1 otherwise. - {{"--search" | color "yellow" ""}} Use the search list defined in resolv.conf. Defaults to true. Set --search=false to disable search list. - {{"--timeout" | color "yellow" ""}} Specify timeout (in seconds) for the resolver to return a response. - {{"-4 --ipv4" | color "yellow" ""}} Use IPv4 only. - {{"-6 --ipv6" | color "yellow" ""}} Use IPv6 only. + {{"--strategy=STRATEGY" | color "yellow" ""}} Specify strategy to query nameserver listed in etc/resolv.conf. ({{"all, random, first" | color "cyan" ""}}). + {{"--ndots=INT" | color "yellow" ""}} Specify ndots parameter. Takes value from /etc/resolv.conf if using the system namesever or 1 otherwise. + {{"--search" | color "yellow" ""}} Use the search list defined in resolv.conf. Defaults to true. Set --search=false to disable search list. + {{"--timeout" | color "yellow" ""}} Specify timeout (in seconds) for the resolver to return a response. + {{"-4 --ipv4" | color "yellow" ""}} Use IPv4 only. + {{"-6 --ipv6" | color "yellow" ""}} Use IPv6 only. {{ "Output Options" | color "" "heading" }}: {{"-J, --json " | color "yellow" ""}} Format the output as JSON. diff --git a/internal/app/nameservers.go b/internal/app/nameservers.go index 5798e9e..d13d78e 100644 --- a/internal/app/nameservers.go +++ b/internal/app/nameservers.go @@ -2,8 +2,10 @@ package app import ( "fmt" + "math/rand" "net" "net/url" + "time" "github.com/ameshkov/dnsstamps" "github.com/mr-karan/doggo/pkg/config" @@ -29,7 +31,7 @@ func (app *App) LoadNameservers() error { // fallback to system nameserver // in case no nameserver is specified by user. if len(app.Nameservers) == 0 { - ns, ndots, search, err := getDefaultServers() + ns, ndots, search, err := getDefaultServers(app.QueryFlags.Strategy) if err != nil { return fmt.Errorf("error fetching system default nameserver") } @@ -117,18 +119,44 @@ func initNameserver(n string) (models.Nameserver, error) { return ns, nil } -func getDefaultServers() ([]models.Nameserver, int, []string, error) { +func getDefaultServers(strategy string) ([]models.Nameserver, int, []string, error) { + // Load nameservers from `/etc/resolv.conf`. dnsServers, ndots, search, err := config.GetDefaultServers() if err != nil { return nil, 0, nil, err } servers := make([]models.Nameserver, 0, len(dnsServers)) - for _, s := range dnsServers { + + switch strategy { + case "random": + // Choose a random server from the list. + rand.Seed(time.Now().Unix()) + srv := dnsServers[rand.Intn(len(dnsServers))] ns := models.Nameserver{ Type: models.UDPResolver, - Address: net.JoinHostPort(s, models.DefaultUDPPort), + Address: net.JoinHostPort(srv, models.DefaultUDPPort), } servers = append(servers, ns) + + case "first": + // Choose the first from the list, always. + srv := dnsServers[0] + ns := models.Nameserver{ + Type: models.UDPResolver, + Address: net.JoinHostPort(srv, models.DefaultUDPPort), + } + servers = append(servers, ns) + + default: + // Default behaviour is to load all nameservers. + for _, s := range dnsServers { + ns := models.Nameserver{ + Type: models.UDPResolver, + Address: net.JoinHostPort(s, models.DefaultUDPPort), + } + servers = append(servers, ns) + } } + return servers, ndots, search, nil } diff --git a/pkg/models/models.go b/pkg/models/models.go index fce82cd..6d340c2 100644 --- a/pkg/models/models.go +++ b/pkg/models/models.go @@ -35,6 +35,7 @@ type QueryFlags struct { ShowJSON bool `koanf:"json" json:"-"` UseSearchList bool `koanf:"search" json:"-"` ReverseLookup bool `koanf:"reverse" reverse:"-"` + Strategy string `koanf:"strategy" strategy:"-"` } // Nameserver represents the type of Nameserver diff --git a/pkg/resolvers/resolver.go b/pkg/resolvers/resolver.go index 6199e39..7b33245 100644 --- a/pkg/resolvers/resolver.go +++ b/pkg/resolvers/resolver.go @@ -18,6 +18,7 @@ type Options struct { Ndots int Timeout time.Duration Logger *logrus.Logger + Strategy string } // Resolver implements the configuration for a DNS