fix: Make a separate cmd/doggo package for main files

Closes https://github.com/mr-karan/doggo/issues/1
This commit is contained in:
Karan Sharma 2020-12-18 20:18:28 +05:30
parent 724114e144
commit 4d618b892b
11 changed files with 14 additions and 9 deletions

123
cmd/doggo/cli.go Normal file
View file

@ -0,0 +1,123 @@
package main
import (
"os"
"github.com/knadh/koanf"
"github.com/knadh/koanf/providers/posflag"
"github.com/sirupsen/logrus"
flag "github.com/spf13/pflag"
)
var (
// Version and date of the build. This is injected at build-time.
buildVersion = "unknown"
buildDate = "unknown"
)
func main() {
var (
logger = initLogger()
k = koanf.New(".")
)
// Initialize hub.
hub := NewHub(logger, buildVersion)
// Configure Flags.
f := flag.NewFlagSet("config", flag.ContinueOnError)
hub.flag = f
// Custom Help Text.
f.Usage = renderCustomHelp
// Query Options.
f.StringSliceP("query", "q", []string{}, "Domain name to query")
f.StringSliceP("type", "t", []string{}, "Type of DNS record to be queried (A, AAAA, MX etc)")
f.StringSliceP("class", "c", []string{}, "Network class of the DNS record to be queried (IN, CH, HS etc)")
f.StringSliceP("nameserver", "n", []string{}, "Address of the nameserver to send packets to")
// Resolver Options
f.Int("timeout", 5, "Sets the timeout for a query to T seconds. The default timeout is 5 seconds.")
f.Bool("search", true, "Use the search list provided in resolv.conf. It sets the `ndots` parameter as well unless overriden by `ndots` flag.")
f.Int("ndots", 0, "Specify the ndots paramter. 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")
// Output Options
f.BoolP("json", "J", false, "Set the output format as JSON")
f.Bool("time", false, "Display how long it took for the response to arrive")
f.Bool("color", true, "Show colored output")
f.Bool("debug", false, "Enable debug mode")
// Parse and Load Flags.
err := f.Parse(os.Args[1:])
if err != nil {
hub.Logger.WithError(err).Error("error parsing flags")
hub.Logger.Exit(2)
}
if err = k.Load(posflag.Provider(f, ".", k), nil); err != nil {
hub.Logger.WithError(err).Error("error loading flags")
f.Usage()
hub.Logger.Exit(2)
}
// Set log level.
if k.Bool("debug") {
// Set logger level
hub.Logger.SetLevel(logrus.DebugLevel)
} else {
hub.Logger.SetLevel(logrus.InfoLevel)
}
// Unmarshall flags to the hub.
err = k.Unmarshal("", &hub.QueryFlags)
if err != nil {
hub.Logger.WithError(err).Error("error loading args")
hub.Logger.Exit(2)
}
// Load all `non-flag` arguments
// which will be parsed separately.
hub.UnparsedArgs = f.Args()
// Parse Query Args.
err = hub.loadQueryArgs()
if err != nil {
hub.Logger.WithError(err).Error("error parsing flags/arguments")
hub.Logger.Exit(2)
}
// Load Nameservers.
err = hub.loadNameservers()
if err != nil {
hub.Logger.WithError(err).Error("error loading nameservers")
hub.Logger.Exit(2)
}
// Load Resolvers.
err = hub.loadResolvers()
if err != nil {
hub.Logger.WithError(err).Error("error loading resolver")
hub.Logger.Exit(2)
}
// Run the app.
hub.Logger.Debug("Starting doggo 🐶")
if len(hub.QueryFlags.QNames) == 0 {
f.Usage()
hub.Logger.Exit(0)
}
// Resolve Queries.
responses, err := hub.Lookup()
if err != nil {
hub.Logger.WithError(err).Error("error looking up DNS records")
hub.Logger.Exit(2)
}
//Send the output.
hub.Output(responses)
// Quitting.
hub.Logger.Exit(0)
}

101
cmd/doggo/help.go Normal file
View file

@ -0,0 +1,101 @@
package main
import (
"os"
"text/template"
"github.com/fatih/color"
)
// appHelpTextTemplate is the text/template to customise the Help output.
// Uses text/template to render templates.
var appHelpTextTemplate = `{{ "NAME" | color "" "heading" }}:
{{ .Name | color "green" "bold" }} 🐶 {{.Description}}
{{ "USAGE" | color "" "heading" }}:
{{ .Name | color "green" "bold" }} [--] {{ "[query options]" | color "yellow" "" }} {{ "[arguments...]" | color "cyan" "" }}
{{ "VERSION" | color "" "heading" }}:
{{.Version | color "red" "" }} - {{.Date | color "red" ""}}
{{ "EXAMPLES" | color "" "heading" }}:
{{ .Name | color "green" "bold" }} {{ "mrkaran.dev" | color "cyan" "" }} Query a domain using defaults.
{{ .Name | color "green" "bold" }} {{ "mrkaran.dev CNAME" | color "cyan" "" }} Looks up 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.
{{ "Free Form Arguments" | color "" "heading" }}:
Supply hostnames, query types, classes without any flag. For eg:
{{ .Name | color "green" "bold" }} {{"mrkaran.dev A @1.1.1.1" | color "cyan" "" }}
{{ "Transport Options" | color "" "heading" }}:
Based on the URL scheme the correct resolver is chosen.
Fallbacks to UDP resolver if no scheme is present.
{{"@udp://" | color "yellow" ""}} eg: @1.1.1.1 initiates a {{"UDP" | color "cyan" ""}} resolver for 1.1.1.1:53.
{{"@tcp://" | color "yellow" ""}} eg: @1.1.1.1 initiates a {{"TCP" | color "cyan" ""}} resolver for 1.1.1.1:53.
{{"@https://" | color "yellow" ""}} eg: @https://cloudflare-dns.com/dns-query initiates a {{"DOH" | color "cyan" ""}} resolver for Cloudflare DoH server.
{{"@tls://" | color "yellow" ""}} eg: @1.1.1.1 initiates a {{"DoT" | color "cyan" ""}} resolver for 1.1.1.1:853.
{{ "Query Options" | color "" "heading" }}:
{{"-q, --query=HOSTNAME" | color "yellow" ""}} Hostname to query the DNS records for (eg {{"mrkaran.dev" | color "cyan" ""}}).
{{"-t, --type=TYPE" | color "yellow" ""}} Type of the DNS Record ({{"A, MX, NS" | color "cyan" ""}} etc).
{{"-n, --nameserver=ADDR" | color "yellow" ""}} Address of a specific nameserver to send queries to ({{"9.9.9.9, 8.8.8.8" | color "cyan" ""}} etc).
{{"-c, --class=CLASS" | color "yellow" ""}} Network class of the DNS record ({{"IN, CH, HS" | color "cyan" ""}} etc).
{{ "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.
{{ "Output Options" | color "" "heading" }}:
{{"-J, --json " | color "yellow" ""}} Format the output as JSON.
{{"--color " | color "yellow" ""}} Defaults to true. Set --color=false to disable colored output.
{{"--debug " | color "yellow" ""}} Enable debug logging.
{{"--time" | color "yellow" ""}} Shows how long the response took from the server.
`
func renderCustomHelp() {
helpTmplVars := map[string]string{
"Name": "doggo",
"Description": "DNS Client for Humans",
"Version": buildVersion,
"Date": buildDate,
}
tmpl, err := template.New("test").Funcs(template.FuncMap{
"color": func(clr string, format string, str string) string {
formatter := color.New()
switch c := clr; c {
case "yellow":
formatter = formatter.Add(color.FgYellow)
case "red":
formatter = formatter.Add(color.FgRed)
case "cyan":
formatter = formatter.Add(color.FgCyan)
case "green":
formatter = formatter.Add(color.FgGreen)
}
switch f := format; f {
case "bold":
formatter = formatter.Add(color.Bold)
case "underline":
formatter = formatter.Add(color.Underline)
case "heading":
formatter = formatter.Add(color.Bold, color.Underline)
}
return formatter.SprintFunc()(str)
},
}).Parse(appHelpTextTemplate)
if err != nil {
// should ideally never happen.
panic(err)
}
err = tmpl.Execute(os.Stdout, helpTmplVars)
if err != nil {
// should ideally never happen.
panic(err)
}
os.Exit(0)
}

73
cmd/doggo/hub.go Normal file
View file

@ -0,0 +1,73 @@
package main
import (
"time"
"github.com/miekg/dns"
"github.com/mr-karan/doggo/pkg/resolvers"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"
)
// Hub represents the structure for all app wide configuration.
type Hub struct {
Logger *logrus.Logger
Version string
QueryFlags QueryFlags
UnparsedArgs []string
Questions []dns.Question
Resolver []resolvers.Resolver
Nameservers []Nameserver
flag *pflag.FlagSet
}
// QueryFlags is used store the query params
// supplied by the user.
type QueryFlags struct {
QNames []string `koanf:"query"`
QTypes []string `koanf:"type"`
QClasses []string `koanf:"class"`
Nameservers []string `koanf:"nameserver"`
UseIPv4 bool `koanf:"ipv4"`
UseIPv6 bool `koanf:"ipv6"`
DisplayTimeTaken bool `koanf:"time"`
ShowJSON bool `koanf:"json"`
UseSearchList bool `koanf:"search"`
Ndots int `koanf:"ndots"`
Color bool `koanf:"color"`
Timeout time.Duration `koanf:"timeout"`
isNdotsSet bool
}
// Nameserver represents the type of Nameserver
// along with the server address.
type Nameserver struct {
Address string
Type string
}
// NewHub initializes an instance of Hub which holds app wide configuration.
func NewHub(logger *logrus.Logger, buildVersion string) *Hub {
hub := &Hub{
Logger: logger,
Version: buildVersion,
QueryFlags: QueryFlags{
QNames: []string{},
QTypes: []string{},
QClasses: []string{},
Nameservers: []string{},
},
Nameservers: []Nameserver{},
}
return hub
}
// initLogger initializes logger
func initLogger() *logrus.Logger {
logger := logrus.New()
logger.SetFormatter(&logrus.TextFormatter{
FullTimestamp: true,
DisableLevelTruncation: true,
})
return logger
}

90
cmd/doggo/lookup.go Normal file
View file

@ -0,0 +1,90 @@
package main
import (
"runtime"
"strings"
"github.com/miekg/dns"
"github.com/mr-karan/doggo/pkg/resolvers"
"github.com/sirupsen/logrus"
)
// Lookup sends the DNS queries to the server.
// It prepares a list of `dns.Questions` and sends
// to all resolvers. It returns a list of []resolver.Response from
// each resolver
func (hub *Hub) Lookup() ([][]resolvers.Response, error) {
questions, err := hub.prepareQuestions()
if err != nil {
return nil, err
}
hub.Questions = questions
// for each type of resolver do a DNS lookup
responses := make([][]resolvers.Response, 0, len(hub.Questions))
for _, r := range hub.Resolver {
resp, err := r.Lookup(hub.Questions)
if err != nil {
return nil, err
}
responses = append(responses, resp)
}
return responses, nil
}
// prepareQuestions takes a list of hostnames and some
// additional options and returns a list of all possible
// `dns.Questions`.
func (hub *Hub) prepareQuestions() ([]dns.Question, error) {
var (
questions []dns.Question
)
for _, name := range hub.QueryFlags.QNames {
var (
domains []string
)
// If `search` flag is specified then fetch the search list
// from `resolv.conf` and set the
if hub.QueryFlags.UseSearchList {
list, err := fetchDomainList(name, hub.QueryFlags.Ndots)
if err != nil {
return nil, err
}
domains = list
} else {
domains = []string{dns.Fqdn(name)}
}
for _, d := range domains {
hub.Logger.WithFields(logrus.Fields{
"domain": d,
"ndots": hub.QueryFlags.Ndots,
}).Debug("Attmepting to resolve")
question := dns.Question{
Name: d,
}
// iterate on a list of query types.
for _, q := range hub.QueryFlags.QTypes {
question.Qtype = dns.StringToType[strings.ToUpper(q)]
// iterate on a list of query classes.
for _, c := range hub.QueryFlags.QClasses {
question.Qclass = dns.StringToClass[strings.ToUpper(c)]
// append a new question for each possible pair.
questions = append(questions, question)
}
}
}
}
return questions, nil
}
func fetchDomainList(d string, ndots int) ([]string, error) {
if runtime.GOOS == "windows" {
// TODO: Add a method for reading system default nameserver in windows.
return []string{d}, nil
}
cfg, err := dns.ClientConfigFromFile(DefaultResolvConfPath)
if err != nil {
return nil, err
}
cfg.Ndots = ndots
return cfg.NameList(d), nil
}

129
cmd/doggo/nameservers.go Normal file
View file

@ -0,0 +1,129 @@
package main
import (
"errors"
"fmt"
"net"
"net/url"
"runtime"
"github.com/miekg/dns"
)
const (
//DefaultResolvConfPath specifies path to default resolv config file on UNIX.
DefaultResolvConfPath = "/etc/resolv.conf"
// DefaultTLSPort specifies the default port for a DNS server connecting over TCP over TLS
DefaultTLSPort = "853"
// DefaultUDPPort specifies the default port for a DNS server connecting over UDP
DefaultUDPPort = "53"
// DefaultTCPPort specifies the default port for a DNS server connecting over TCP
DefaultTCPPort = "53"
UDPResolver = "udp"
DOHResolver = "doh"
TCPResolver = "tcp"
DOTResolver = "dot"
)
// loadNameservers reads all the user given
// nameservers and loads to Hub.
func (hub *Hub) loadNameservers() error {
for _, srv := range hub.QueryFlags.Nameservers {
ns, err := initNameserver(srv)
if err != nil {
return fmt.Errorf("error parsing nameserver: %s", srv)
}
// check if properly initialised.
if ns.Address != "" && ns.Type != "" {
hub.Nameservers = append(hub.Nameservers, ns)
}
}
// fallback to system nameserver
// in case no nameserver is specified by user.
if len(hub.Nameservers) == 0 {
ns, ndots, err := getDefaultServers()
if err != nil {
return fmt.Errorf("error fetching system default nameserver")
}
if hub.QueryFlags.Ndots == 0 {
hub.QueryFlags.Ndots = ndots
}
hub.Nameservers = append(hub.Nameservers, ns...)
}
return nil
}
// getDefaultServers reads the `resolv.conf`
// file and returns a list of nameservers.
func getDefaultServers() ([]Nameserver, int, error) {
if runtime.GOOS == "windows" {
// TODO: Add a method for reading system default nameserver in windows.
return nil, 0, errors.New(`unable to read default nameservers in this machine`)
}
// if no nameserver is provided, take it from `resolv.conf`
cfg, err := dns.ClientConfigFromFile(DefaultResolvConfPath)
if err != nil {
return nil, 0, err
}
servers := make([]Nameserver, 0, len(cfg.Servers))
for _, s := range cfg.Servers {
var (
ip = net.ParseIP(s)
addr string
)
// handle IPv6
if ip != nil && ip.To4() != nil {
addr = fmt.Sprintf("%s:%s", s, cfg.Port)
} else {
addr = fmt.Sprintf("[%s]:%s", s, cfg.Port)
}
ns := Nameserver{
Type: UDPResolver,
Address: addr,
}
servers = append(servers, ns)
}
return servers, cfg.Ndots, nil
}
func initNameserver(n string) (Nameserver, error) {
// Instantiate a UDP resolver with default port as a fallback.
ns := Nameserver{
Type: UDPResolver,
Address: net.JoinHostPort(n, DefaultUDPPort),
}
u, err := url.Parse(n)
if err != nil {
return ns, err
}
if u.Scheme == "https" {
ns.Type = DOHResolver
ns.Address = u.String()
}
if u.Scheme == "tls" {
ns.Type = DOTResolver
if u.Port() == "" {
ns.Address = net.JoinHostPort(u.Hostname(), DefaultTLSPort)
} else {
ns.Address = net.JoinHostPort(u.Hostname(), u.Port())
}
}
if u.Scheme == "tcp" {
ns.Type = TCPResolver
if u.Port() == "" {
ns.Address = net.JoinHostPort(u.Hostname(), DefaultTCPPort)
} else {
ns.Address = net.JoinHostPort(u.Hostname(), u.Port())
}
}
if u.Scheme == "udp" {
ns.Type = UDPResolver
if u.Port() == "" {
ns.Address = net.JoinHostPort(u.Hostname(), DefaultUDPPort)
} else {
ns.Address = net.JoinHostPort(u.Hostname(), u.Port())
}
}
return ns, nil
}

223
cmd/doggo/output.go Normal file
View file

@ -0,0 +1,223 @@
package main
import (
"encoding/json"
"fmt"
"os"
"strconv"
"github.com/fatih/color"
"github.com/miekg/dns"
"github.com/mr-karan/doggo/pkg/resolvers"
"github.com/olekukonko/tablewriter"
)
type Output struct {
Name string `json:"name"`
Type string `json:"type"`
Class string `json:"class"`
TTL string `json:"ttl"`
Address string `json:"address"`
TimeTaken string `json:"rtt"`
Nameserver string `json:"nameserver"`
}
type Query struct {
Name string `json:"name"`
Type string `json:"type"`
Class string `json:"class"`
}
type Response struct {
Output []Output `json:"answers"`
Queries []Query `json:"queries"`
}
type JSONResponse struct {
Response `json:"responses"`
}
func (hub *Hub) outputJSON(out []Output) {
// get the questions
queries := make([]Query, 0)
for _, ques := range hub.Questions {
q := Query{
Name: ques.Name,
Type: dns.TypeToString[ques.Qtype],
Class: dns.ClassToString[ques.Qclass],
}
queries = append(queries, q)
}
resp := JSONResponse{
Response{
Output: out,
Queries: queries,
},
}
res, err := json.Marshal(resp)
if err != nil {
hub.Logger.WithError(err).Error("unable to output data in JSON")
hub.Logger.Exit(-1)
}
fmt.Printf("%s", res)
}
func (hub *Hub) outputTerminal(out []Output) {
green := color.New(color.FgGreen, color.Bold).SprintFunc()
blue := color.New(color.FgBlue, color.Bold).SprintFunc()
yellow := color.New(color.FgYellow, color.Bold).SprintFunc()
cyan := color.New(color.FgCyan, color.Bold).SprintFunc()
red := color.New(color.FgRed, color.Bold).SprintFunc()
if !hub.QueryFlags.Color {
color.NoColor = true // disables colorized output
}
table := tablewriter.NewWriter(os.Stdout)
header := []string{"Name", "Type", "Class", "TTL", "Address", "Nameserver"}
if hub.QueryFlags.DisplayTimeTaken {
header = append(header, "Time Taken")
}
table.SetHeader(header)
table.SetAutoWrapText(false)
table.SetAutoFormatHeaders(true)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetCenterSeparator("")
table.SetColumnSeparator("")
table.SetRowSeparator("")
table.SetHeaderLine(false)
table.SetBorder(false)
table.SetTablePadding("\t") // pad with tabs
table.SetNoWhiteSpace(true)
for _, o := range out {
var typOut string
switch typ := o.Type; typ {
case "A":
typOut = blue(o.Type)
case "AAAA":
typOut = blue(o.Type)
case "MX":
typOut = red(o.Type)
case "NS":
typOut = cyan(o.Type)
case "CNAME":
typOut = yellow(o.Type)
case "TXT":
typOut = yellow(o.Type)
case "SOA":
typOut = red(o.Type)
default:
typOut = blue(o.Type)
}
output := []string{green(o.Name), typOut, o.Class, o.TTL, o.Address, o.Nameserver}
// Print how long it took
if hub.QueryFlags.DisplayTimeTaken {
output = append(output, o.TimeTaken)
}
table.Append(output)
}
table.Render()
}
// Output takes a list of `dns.Answers` and based
// on the output format specified displays the information.
func (hub *Hub) Output(responses [][]resolvers.Response) {
out := collectOutput(responses)
if hub.QueryFlags.ShowJSON {
hub.outputJSON(out)
} else {
hub.outputTerminal(out)
}
}
func collectOutput(responses [][]resolvers.Response) []Output {
var out []Output
// for each resolver
for _, rslvr := range responses {
// get the response
for _, r := range rslvr {
var addr string
for _, ns := range r.Message.Ns {
// check for SOA record
soa, ok := ns.(*dns.SOA)
if !ok {
// skip this message
continue
}
addr = soa.Ns + " " + soa.Mbox +
" " + strconv.FormatInt(int64(soa.Serial), 10) +
" " + strconv.FormatInt(int64(soa.Refresh), 10) +
" " + strconv.FormatInt(int64(soa.Retry), 10) +
" " + strconv.FormatInt(int64(soa.Expire), 10) +
" " + strconv.FormatInt(int64(soa.Minttl), 10)
h := ns.Header()
name := h.Name
qclass := dns.Class(h.Class).String()
ttl := strconv.FormatInt(int64(h.Ttl), 10) + "s"
qtype := dns.Type(h.Rrtype).String()
rtt := fmt.Sprintf("%dms", r.RTT.Milliseconds())
o := Output{
Name: name,
Type: qtype,
TTL: ttl,
Class: qclass,
Address: addr,
TimeTaken: rtt,
Nameserver: r.Nameserver,
}
out = append(out, o)
}
for _, a := range r.Message.Answer {
switch t := a.(type) {
case *dns.A:
addr = t.A.String()
case *dns.AAAA:
addr = t.AAAA.String()
case *dns.CNAME:
addr = t.Target
case *dns.CAA:
addr = t.Tag + " " + t.Value
case *dns.HINFO:
addr = t.Cpu + " " + t.Os
case *dns.PTR:
addr = t.Ptr
case *dns.SRV:
addr = strconv.Itoa(int(t.Priority)) + " " +
strconv.Itoa(int(t.Weight)) + " " +
t.Target + ":" + strconv.Itoa(int(t.Port))
case *dns.TXT:
addr = t.String()
case *dns.NS:
addr = t.Ns
case *dns.MX:
addr = strconv.Itoa(int(t.Preference)) + " " + t.Mx
case *dns.SOA:
addr = t.String()
case *dns.NAPTR:
addr = t.String()
}
h := a.Header()
name := h.Name
qclass := dns.Class(h.Class).String()
ttl := strconv.FormatInt(int64(h.Ttl), 10) + "s"
qtype := dns.Type(h.Rrtype).String()
rtt := fmt.Sprintf("%dms", r.RTT.Milliseconds())
o := Output{
Name: name,
Type: qtype,
TTL: ttl,
Class: qclass,
Address: addr,
TimeTaken: rtt,
Nameserver: r.Nameserver,
}
out = append(out, o)
}
}
}
return out
}

73
cmd/doggo/parse.go Normal file
View file

@ -0,0 +1,73 @@
package main
import (
"strings"
"github.com/miekg/dns"
flag "github.com/spf13/pflag"
)
func (hub *Hub) loadQueryArgs() error {
// Appends a list of unparsed args to
// internal query flags.
err := hub.loadUnparsedArgs()
if err != nil {
return err
}
// check if ndots is set
hub.QueryFlags.isNdotsSet = isFlagPassed("ndots", hub.flag)
// Load all fallbacks in internal query flags.
hub.loadFallbacks()
return nil
}
// loadUnparsedArgs tries to parse all the arguments
// which are unparsed by `flag` library. These arguments don't have any specific
// order so we have to deduce based on the pattern of argument.
// For eg, a nameserver must always begin with `@`. In this
// pattern we deduce the arguments and append it to the
// list of internal query flags.
// In case an argument isn't able to fit in any of the existing
// pattern it is considered to be a "hostname".
// Eg of unparsed argument: `dig mrkaran.dev @1.1.1.1 AAAA`
// where `@1.1.1.1` and `AAAA` are "unparsed" args.
func (hub *Hub) loadUnparsedArgs() error {
for _, arg := range hub.UnparsedArgs {
if strings.HasPrefix(arg, "@") {
hub.QueryFlags.Nameservers = append(hub.QueryFlags.Nameservers, strings.Trim(arg, "@"))
} else if _, ok := dns.StringToType[strings.ToUpper(arg)]; ok {
hub.QueryFlags.QTypes = append(hub.QueryFlags.QTypes, arg)
} else if _, ok := dns.StringToClass[strings.ToUpper(arg)]; ok {
hub.QueryFlags.QClasses = append(hub.QueryFlags.QClasses, arg)
} else {
// if nothing matches, consider it's a query name.
hub.QueryFlags.QNames = append(hub.QueryFlags.QNames, arg)
}
}
return nil
}
// loadFallbacks sets fallbacks for options
// that are not specified by the user but necessary
// for the resolver.
func (hub *Hub) loadFallbacks() {
if len(hub.QueryFlags.QTypes) == 0 {
hub.QueryFlags.QTypes = append(hub.QueryFlags.QTypes, "A")
}
if len(hub.QueryFlags.QClasses) == 0 {
hub.QueryFlags.QClasses = append(hub.QueryFlags.QClasses, "IN")
}
}
// isFlagPassed checks if the flag is supplied by
//user or not.
func isFlagPassed(name string, f *flag.FlagSet) bool {
found := false
f.Visit(func(f *flag.Flag) {
if f.Name == name {
found = true
}
})
return found
}

68
cmd/doggo/resolver.go Normal file
View file

@ -0,0 +1,68 @@
package main
import (
"time"
"github.com/mr-karan/doggo/pkg/resolvers"
)
// loadResolvers loads differently configured
// resolvers based on a list of nameserver.
func (hub *Hub) loadResolvers() error {
// for each nameserver, initialise the correct resolver
for _, ns := range hub.Nameservers {
if ns.Type == DOHResolver {
hub.Logger.Debug("initiating DOH resolver")
rslvr, err := resolvers.NewDOHResolver(ns.Address, resolvers.DOHResolverOpts{
Timeout: hub.QueryFlags.Timeout * time.Second,
})
if err != nil {
return err
}
hub.Resolver = append(hub.Resolver, rslvr)
}
if ns.Type == DOTResolver {
hub.Logger.Debug("initiating DOT resolver")
rslvr, err := resolvers.NewClassicResolver(ns.Address, resolvers.ClassicResolverOpts{
IPv4Only: hub.QueryFlags.UseIPv4,
IPv6Only: hub.QueryFlags.UseIPv6,
Timeout: hub.QueryFlags.Timeout * time.Second,
UseTLS: true,
UseTCP: true,
})
if err != nil {
return err
}
hub.Resolver = append(hub.Resolver, rslvr)
}
if ns.Type == TCPResolver {
hub.Logger.Debug("initiating TCP resolver")
rslvr, err := resolvers.NewClassicResolver(ns.Address, resolvers.ClassicResolverOpts{
IPv4Only: hub.QueryFlags.UseIPv4,
IPv6Only: hub.QueryFlags.UseIPv6,
Timeout: hub.QueryFlags.Timeout * time.Second,
UseTLS: false,
UseTCP: true,
})
if err != nil {
return err
}
hub.Resolver = append(hub.Resolver, rslvr)
}
if ns.Type == UDPResolver {
hub.Logger.Debug("initiating UDP resolver")
rslvr, err := resolvers.NewClassicResolver(ns.Address, resolvers.ClassicResolverOpts{
IPv4Only: hub.QueryFlags.UseIPv4,
IPv6Only: hub.QueryFlags.UseIPv6,
Timeout: hub.QueryFlags.Timeout * time.Second,
UseTLS: false,
UseTCP: false,
})
if err != nil {
return err
}
hub.Resolver = append(hub.Resolver, rslvr)
}
}
return nil
}