diff --git a/cmd/api/api.go b/cmd/api/api.go index d3e612c..b7f435f 100644 --- a/cmd/api/api.go +++ b/cmd/api/api.go @@ -6,7 +6,7 @@ import ( "net/http" "time" - "github.com/mr-karan/doggo/internal/app" + "github.com/mr-karan/doggo/internal/hub" "github.com/mr-karan/doggo/pkg/utils" "github.com/mr-karan/logf" @@ -31,7 +31,7 @@ func main() { initConfig() // Initialize app. - app := app.New(logger, buildString) + app := hub.New(logger, buildString) // Register router instance. r := chi.NewRouter() diff --git a/cmd/api/handlers.go b/cmd/api/handlers.go index 5adff0b..0464c02 100644 --- a/cmd/api/handlers.go +++ b/cmd/api/handlers.go @@ -8,7 +8,7 @@ import ( "net/http" "time" - "github.com/mr-karan/doggo/internal/app" + "github.com/mr-karan/doggo/internal/hub" "github.com/mr-karan/doggo/pkg/models" "github.com/mr-karan/doggo/pkg/resolvers" ) @@ -21,7 +21,7 @@ type httpResp struct { func handleIndexAPI(w http.ResponseWriter, r *http.Request) { var ( - app = r.Context().Value("app").(app.App) + app = r.Context().Value("app").(hub.Hub) ) sendResponse(w, http.StatusOK, fmt.Sprintf("Welcome to Doggo API. Version: %s", app.Version)) @@ -35,21 +35,21 @@ func handleHealthCheck(w http.ResponseWriter, r *http.Request) { func handleLookup(w http.ResponseWriter, r *http.Request) { var ( - app = r.Context().Value("app").(app.App) + app = r.Context().Value("app").(hub.Hub) ) // Read body. b, err := ioutil.ReadAll(r.Body) defer r.Body.Close() if err != nil { - app.Logger.WithError(err).Error("error reading request body") + app.Log.WithError(err).Error("error reading request body") sendErrorResponse(w, fmt.Sprintf("Invalid JSON payload"), http.StatusBadRequest, nil) return } // Prepare query flags. var qFlags models.QueryFlags if err := json.Unmarshal(b, &qFlags); err != nil { - app.Logger.WithError(err).Error("error unmarshalling payload") + app.Log.WithError(err).Error("error unmarshalling payload") sendErrorResponse(w, fmt.Sprintf("Invalid JSON payload"), http.StatusBadRequest, nil) return } @@ -69,7 +69,7 @@ func handleLookup(w http.ResponseWriter, r *http.Request) { // Load Nameservers. err = app.LoadNameservers() if err != nil { - app.Logger.WithError(err).Error("error loading nameservers") + app.Log.WithError(err).Error("error loading nameservers") sendErrorResponse(w, fmt.Sprintf("Error looking up for records."), http.StatusInternalServerError, nil) return } @@ -82,10 +82,10 @@ func handleLookup(w http.ResponseWriter, r *http.Request) { SearchList: app.ResolverOpts.SearchList, Ndots: app.ResolverOpts.Ndots, Timeout: app.QueryFlags.Timeout * time.Second, - Logger: app.Logger, + Logger: app.Log, }) if err != nil { - app.Logger.WithError(err).Error("error loading resolver") + app.Log.WithError(err).Error("error loading resolver") sendErrorResponse(w, fmt.Sprintf("Error looking up for records."), http.StatusInternalServerError, nil) return } @@ -96,7 +96,7 @@ func handleLookup(w http.ResponseWriter, r *http.Request) { for _, rslv := range app.Resolvers { resp, err := rslv.Lookup(q) if err != nil { - app.Logger.WithError(err).Error("error looking up DNS records") + app.Log.WithError(err).Error("error looking up DNS records") sendErrorResponse(w, fmt.Sprintf("Error looking up for records."), http.StatusInternalServerError, nil) return } @@ -108,7 +108,7 @@ func handleLookup(w http.ResponseWriter, r *http.Request) { } // wrap is a middleware that wraps HTTP handlers and injects the "app" context. -func wrap(app app.App, next http.HandlerFunc) http.HandlerFunc { +func wrap(app hub.Hub, next http.HandlerFunc) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), "app", app) next.ServeHTTP(w, r.WithContext(ctx)) diff --git a/cmd/doggo/cli.go b/cmd/doggo-v1/cli.go similarity index 98% rename from cmd/doggo/cli.go rename to cmd/doggo-v1/cli.go index eb5b661..6b9bf19 100644 --- a/cmd/doggo/cli.go +++ b/cmd/doggo-v1/cli.go @@ -8,8 +8,8 @@ import ( "github.com/knadh/koanf" "github.com/knadh/koanf/providers/posflag" "github.com/mr-karan/doggo/internal/app" - "github.com/mr-karan/doggo/pkg/resolvers" - "github.com/mr-karan/doggo/pkg/utils" + "github.com/mr-karan/doggo/internal/resolvers" + "github.com/mr-karan/doggo/internal/utils" "github.com/mr-karan/logf" flag "github.com/spf13/pflag" ) diff --git a/cmd/doggo-v1/help.go b/cmd/doggo-v1/help.go new file mode 100644 index 0000000..511531a --- /dev/null +++ b/cmd/doggo-v1/help.go @@ -0,0 +1,108 @@ +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" "" }} + +{{ "EXAMPLES" | color "" "heading" }}: + {{ .Name | color "green" "bold" }} {{ "mrkaran.dev" | color "cyan" "" }} {{"\t"}} Query a domain using defaults. + {{ .Name | color "green" "bold" }} {{ "mrkaran.dev CNAME" | color "cyan" "" }} {{"\t"}} Looks up for a CNAME record. + {{ .Name | color "green" "bold" }} {{ "mrkaran.dev MX @9.9.9.9" | color "cyan" "" }} {{"\t"}} Uses a custom DNS resolver. + {{ .Name | color "green" "bold" }} {{"-q mrkaran.dev -t MX -n 1.1.1.1" | color "yellow" ""}} {{"\t"}} 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: @tcp://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: @tls://1.1.1.1 initiates a {{"DoT" | color "cyan" ""}} resolver for 1.1.1.1:853. + {{"@sdns://" | color "yellow" ""}} initiates a {{"DNSCrypt" | color "cyan" ""}} or {{"DoH" | color "cyan" ""}} resolver using its DNS stamp. + {{"@quic://" | color "yellow" ""}} initiates a {{"DOQ" | color "cyan" ""}} resolver. + +{{ "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). + {{"-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" }}: + {{"--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. + {{"--ndots=INT" | color "yellow" ""}} Specify ndots parameter. Takes value from /etc/resolv.conf if using the system namesever or 1 otherwise. + {{"--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. + +{{ "Output Options" | color "" "heading" }}: + {{"-J, --json " | color "yellow" ""}} Format the output as JSON. + {{"--short" | color "yellow" ""}} Short output format. Shows only the response section. + {{"--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": buildString, + } + 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) +} diff --git a/cmd/doggo/parse.go b/cmd/doggo-v1/parse.go similarity index 100% rename from cmd/doggo/parse.go rename to cmd/doggo-v1/parse.go diff --git a/cmd/doggo/init.go b/cmd/doggo/init.go new file mode 100644 index 0000000..fa99d8e --- /dev/null +++ b/cmd/doggo/init.go @@ -0,0 +1,10 @@ +package main + +import "github.com/mr-karan/logf" + +func initLogger() *logf.Logger { + logf := logf.New() + logf.SetColorOutput(true) + + return logf +} diff --git a/cmd/doggo/main.go b/cmd/doggo/main.go new file mode 100644 index 0000000..458b45b --- /dev/null +++ b/cmd/doggo/main.go @@ -0,0 +1,77 @@ +package main + +import ( + "fmt" + "os" + "sort" + + "github.com/urfave/cli/v2" +) + +var ( + buildString = "unknown" + lo = initLogger() +) + +func main() { + // Intialize new CLI app. + app := cli.NewApp() + app.Name = "doggo" + app.Usage = "DNS Client for Humans" + app.Version = buildString + + // Query Options. + queryFlags := []cli.Flag{ + &cli.StringSliceFlag{ + Name: "query", + Value: cli.NewStringSlice(), + Aliases: []string{"q"}, + Usage: "Hostname to query the DNS records for (eg mrkaran.dev)", + }, + &cli.StringSliceFlag{ + Name: "type", + Value: cli.NewStringSlice(), + Aliases: []string{"t"}, + Usage: "Type of DNS record to be queried (A, AAAA, MX etc)", + }, + &cli.StringSliceFlag{ + Name: "class", + Value: cli.NewStringSlice(), + Aliases: []string{"c"}, + Usage: "Network class of the DNS record to be queried (IN, CH, HS etc)", + }, + &cli.StringSliceFlag{ + Name: "nameservers", + Value: cli.NewStringSlice(), + Aliases: []string{"n"}, + Usage: "DNS Server address to send queries (eg 1.1.1.1, 8.8.8.8)", + }, + &cli.BoolFlag{ + Name: "reverse", + Value: false, + Aliases: []string{"x"}, + Usage: "Performs a DNS Lookup for an IPv4 or IPv6 address. Sets the query type and class to PTR and IN respectively", + }, + } + + // Resolver Options. + + cli.AppHelpTemplate = appHelpTextTemplate + + app.Flags = append(app.Flags, queryFlags...) + + sort.Sort(cli.FlagsByName(app.Flags)) + + // // Define actions. + app.Action = Lookup + + // Run the app. + if err := app.Run(os.Args); err != nil { + lo.Fatal(err.Error()) + } +} + +func Lookup(c *cli.Context) error { + fmt.Println(c.StringSlice("query")) + return nil +} diff --git a/go.mod b/go.mod index 725302c..aa8a6bd 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/mr-karan/logf v0.3.2 github.com/olekukonko/tablewriter v0.0.5 github.com/spf13/pflag v1.0.5 + github.com/urfave/cli/v2 v2.10.3 golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b ) @@ -21,6 +22,7 @@ require ( github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect github.com/cheekybits/genny v1.0.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect @@ -36,6 +38,8 @@ require ( github.com/onsi/ginkgo v1.16.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/net v0.0.0-20220630215102-69896b714898 // indirect diff --git a/go.sum b/go.sum index 1ddfdeb..6c24acd 100644 --- a/go.sum +++ b/go.sum @@ -41,6 +41,8 @@ github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitf github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -183,8 +185,6 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mr-karan/logf v0.3.1 h1:IEv+xcatG7lY9w/EQ/QVM9FNxbR17USaVRA+WOKPb9o= -github.com/mr-karan/logf v0.3.1/go.mod h1:644G+9amD9WMHx0SnzpXksL9iN613UkryU+8RBwsuHM= github.com/mr-karan/logf v0.3.2 h1:Hk/77c6nUdXBw+bNMcyVN2vmlaRQjBC9Qu6SVv5QMdg= github.com/mr-karan/logf v0.3.2/go.mod h1:644G+9amD9WMHx0SnzpXksL9iN613UkryU+8RBwsuHM= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= @@ -225,6 +225,8 @@ github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8d github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -262,8 +264,12 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/urfave/cli/v2 v2.10.3 h1:oi571Fxz5aHugfBAJd5nkwSk3fzATXtMlpxdLylSCMo= +github.com/urfave/cli/v2 v2.10.3/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= @@ -425,8 +431,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/app/app.go b/internal/app/app.go deleted file mode 100644 index a0cac0b..0000000 --- a/internal/app/app.go +++ /dev/null @@ -1,55 +0,0 @@ -package app - -import ( - "math/rand" - "time" - - "github.com/miekg/dns" - "github.com/mr-karan/doggo/pkg/models" - "github.com/mr-karan/doggo/pkg/resolvers" - "github.com/mr-karan/logf" -) - -// App represents the structure for all app wide configuration. -type App struct { - Logger *logf.Logger - Version string - QueryFlags models.QueryFlags - Questions []dns.Question - Resolvers []resolvers.Resolver - ResolverOpts resolvers.Options - Nameservers []models.Nameserver -} - -// NewApp initializes an instance of App which holds app wide configuration. -func New(logger *logf.Logger, buildVersion string) App { - app := App{ - Logger: logger, - Version: buildVersion, - QueryFlags: models.QueryFlags{ - QNames: []string{}, - QTypes: []string{}, - QClasses: []string{}, - Nameservers: []string{}, - }, - Nameservers: []models.Nameserver{}, - } - return app -} - -// Attempts a DNS Lookup with retries for a given Question. -func (app *App) LookupWithRetry(attempts int, resolver resolvers.Resolver, ques dns.Question) (resolvers.Response, error) { - resp, err := resolver.Lookup(ques) - if err != nil { - // Retry lookup. - attempts-- - if attempts > 0 { - // Add some random delay. - time.Sleep(time.Millisecond*300 + (time.Duration(rand.Int63n(int64(time.Millisecond*100))))/2) - app.Logger.Debug("retrying lookup") - return app.LookupWithRetry(attempts, resolver, ques) - } - return resolvers.Response{}, err - } - return resp, nil -} diff --git a/internal/app/questions.go b/internal/app/questions.go deleted file mode 100644 index e6009aa..0000000 --- a/internal/app/questions.go +++ /dev/null @@ -1,54 +0,0 @@ -package app - -import ( - "strings" - - "github.com/miekg/dns" -) - -// LoadFallbacks sets fallbacks for options -// that are not specified by the user but necessary -// for the resolver. -func (app *App) LoadFallbacks() { - if len(app.QueryFlags.QTypes) == 0 { - app.QueryFlags.QTypes = append(app.QueryFlags.QTypes, "A") - } - if len(app.QueryFlags.QClasses) == 0 { - app.QueryFlags.QClasses = append(app.QueryFlags.QClasses, "IN") - } -} - -// PrepareQuestions takes a list of query names, query types and query classes -// and prepare a question for each combination of the above. -func (app *App) PrepareQuestions() { - for _, n := range app.QueryFlags.QNames { - for _, t := range app.QueryFlags.QTypes { - for _, c := range app.QueryFlags.QClasses { - app.Questions = append(app.Questions, dns.Question{ - Name: n, - Qtype: dns.StringToType[strings.ToUpper(t)], - Qclass: dns.StringToClass[strings.ToUpper(c)], - }) - } - } - } -} - -// ReverseLookup is used to perform a reverse DNS Lookup -// using an IPv4 or IPv6 address. -// Query Type is set to PTR, Query Class is set to IN. -// Query Names must be formatted in in-addr.arpa. or ip6.arpa format. -func (app *App) ReverseLookup() { - app.QueryFlags.QTypes = []string{"PTR"} - app.QueryFlags.QClasses = []string{"IN"} - formattedNames := make([]string, 0, len(app.QueryFlags.QNames)) - - for _, n := range app.QueryFlags.QNames { - addr, err := dns.ReverseAddr(n) - if err != nil { - app.Logger.WithError(err).Fatal("error formatting address") - } - formattedNames = append(formattedNames, addr) - } - app.QueryFlags.QNames = formattedNames -} diff --git a/internal/client/client.go b/internal/client/client.go new file mode 100644 index 0000000..0894a33 --- /dev/null +++ b/internal/client/client.go @@ -0,0 +1,53 @@ +package client + +import ( + "math/rand" + "time" + + "github.com/miekg/dns" + "github.com/mr-karan/doggo/internal/models" + "github.com/mr-karan/doggo/internal/resolvers" + "github.com/mr-karan/logf" +) + +// Client represents the structure for all app wide configuration. +type Client struct { + Log *logf.Logger + Version string + QueryFlags models.QueryFlags + Questions []dns.Question + Resolvers []resolvers.Resolver + Nameservers []models.Nameserver +} + +// New initializes an instance of App which holds app wide configuration. +func New(logger *logf.Logger, buildVersion string) Client { + return Client{ + Log: logger, + Version: buildVersion, + QueryFlags: models.QueryFlags{ + QNames: []string{}, + QTypes: []string{}, + QClasses: []string{}, + Nameservers: []string{}, + }, + Nameservers: []models.Nameserver{}, + } +} + +// LookupWithRetry attempts a DNS Lookup with retries for a given Question. +func (hub *Client) LookupWithRetry(attempts int, resolver resolvers.Resolver, ques dns.Question) (resolvers.Response, error) { + resp, err := resolver.Lookup(ques) + if err != nil { + // Retry lookup. + attempts-- + if attempts > 0 { + // Add some random delay. + time.Sleep(time.Millisecond*300 + (time.Duration(rand.Int63n(int64(time.Millisecond*100))))/2) + hub.Log.Debug("retrying lookup") + return hub.LookupWithRetry(attempts, resolver, ques) + } + return resolvers.Response{}, err + } + return resp, nil +} diff --git a/internal/app/nameservers.go b/internal/client/nameservers.go similarity index 83% rename from internal/app/nameservers.go rename to internal/client/nameservers.go index d13d78e..0c63167 100644 --- a/internal/app/nameservers.go +++ b/internal/client/nameservers.go @@ -1,4 +1,4 @@ -package app +package client import ( "fmt" @@ -8,47 +8,47 @@ import ( "time" "github.com/ameshkov/dnsstamps" - "github.com/mr-karan/doggo/pkg/config" - "github.com/mr-karan/doggo/pkg/models" + "github.com/mr-karan/doggo/internal/config" + "github.com/mr-karan/doggo/internal/models" ) // LoadNameservers reads all the user given -// nameservers and loads to App. -func (app *App) LoadNameservers() error { - for _, srv := range app.QueryFlags.Nameservers { +// nameservers and loads to Client. +func (hub *Client) 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 != "" { - app.Nameservers = append(app.Nameservers, ns) + hub.Nameservers = append(hub.Nameservers, ns) } } // Set `ndots` to the user specified value. - app.ResolverOpts.Ndots = app.QueryFlags.Ndots + hub.ResolverOpts.Ndots = hub.QueryFlags.Ndots // fallback to system nameserver // in case no nameserver is specified by user. - if len(app.Nameservers) == 0 { - ns, ndots, search, err := getDefaultServers(app.QueryFlags.Strategy) + if len(hub.Nameservers) == 0 { + ns, ndots, search, err := getDefaultServers(hub.QueryFlags.Strategy) if err != nil { return fmt.Errorf("error fetching system default nameserver") } // `-1` indicates the flag is not set. // use from config if user hasn't specified any value. - if app.ResolverOpts.Ndots == -1 { - app.ResolverOpts.Ndots = ndots + if hub.ResolverOpts.Ndots == -1 { + hub.ResolverOpts.Ndots = ndots } - if len(search) > 0 && app.QueryFlags.UseSearchList { - app.ResolverOpts.SearchList = search + if len(search) > 0 && hub.QueryFlags.UseSearchList { + hub.ResolverOpts.SearchList = search } - app.Nameservers = append(app.Nameservers, ns...) + hub.Nameservers = append(hub.Nameservers, ns...) } // if the user hasn't given any override of `ndots` AND has // given a custom nameserver. Set `ndots` to 1 as the fallback value - if app.ResolverOpts.Ndots == -1 { - app.ResolverOpts.Ndots = 0 + if hub.ResolverOpts.Ndots == -1 { + hub.ResolverOpts.Ndots = 0 } return nil } diff --git a/internal/app/output.go b/internal/client/output.go similarity index 82% rename from internal/app/output.go rename to internal/client/output.go index eec02e1..8764da9 100644 --- a/internal/app/output.go +++ b/internal/client/output.go @@ -1,4 +1,4 @@ -package app +package client import ( "encoding/json" @@ -7,20 +7,20 @@ import ( "github.com/fatih/color" "github.com/miekg/dns" - "github.com/mr-karan/doggo/pkg/resolvers" + "github.com/mr-karan/doggo/internal/resolvers" "github.com/olekukonko/tablewriter" ) -func (app *App) outputJSON(rsp []resolvers.Response) { +func (hub *Client) outputJSON(rsp []resolvers.Response) { // Pretty print with 4 spaces. res, err := json.MarshalIndent(rsp, "", " ") if err != nil { - app.Logger.WithError(err).Fatal("unable to output data in JSON") + hub.Log.WithError(err).Fatal("unable to output data in JSON") } fmt.Printf("%s", res) } -func (app *App) outputShort(rsp []resolvers.Response) { +func (hub *Client) outputShort(rsp []resolvers.Response) { for _, r := range rsp { for _, a := range r.Answers { fmt.Printf("%s\n", a.Address) @@ -28,7 +28,7 @@ func (app *App) outputShort(rsp []resolvers.Response) { } } -func (app *App) outputTerminal(rsp []resolvers.Response) { +func (hub *Client) outputTerminal(rsp []resolvers.Response) { var ( green = color.New(color.FgGreen, color.Bold).SprintFunc() blue = color.New(color.FgBlue, color.Bold).SprintFunc() @@ -39,14 +39,14 @@ func (app *App) outputTerminal(rsp []resolvers.Response) { ) // Disables colorized output if user specified. - if !app.QueryFlags.Color { + if !hub.QueryFlags.Color { color.NoColor = true } // Conditional Time column. table := tablewriter.NewWriter(os.Stdout) header := []string{"Name", "Type", "Class", "TTL", "Address", "Nameserver"} - if app.QueryFlags.DisplayTimeTaken { + if hub.QueryFlags.DisplayTimeTaken { header = append(header, "Time Taken") } @@ -106,7 +106,7 @@ func (app *App) outputTerminal(rsp []resolvers.Response) { } output := []string{green(ans.Name), typOut, ans.Class, ans.TTL, ans.Address, ans.Nameserver} // Print how long it took - if app.QueryFlags.DisplayTimeTaken { + if hub.QueryFlags.DisplayTimeTaken { output = append(output, ans.RTT) } if outputStatus { @@ -124,7 +124,7 @@ func (app *App) outputTerminal(rsp []resolvers.Response) { } output := []string{green(auth.Name), typOut, auth.Class, auth.TTL, auth.MName, auth.Nameserver} // Print how long it took - if app.QueryFlags.DisplayTimeTaken { + if hub.QueryFlags.DisplayTimeTaken { output = append(output, auth.RTT) } if outputStatus { @@ -138,12 +138,12 @@ func (app *App) outputTerminal(rsp []resolvers.Response) { // Output takes a list of `dns.Answers` and based // on the output format specified displays the information. -func (app *App) Output(responses []resolvers.Response) { - if app.QueryFlags.ShowJSON { - app.outputJSON(responses) - } else if app.QueryFlags.ShortOutput { - app.outputShort(responses) +func (hub *Client) Output(responses []resolvers.Response) { + if hub.QueryFlags.ShowJSON { + hub.outputJSON(responses) + } else if hub.QueryFlags.ShortOutput { + hub.outputShort(responses) } else { - app.outputTerminal(responses) + hub.outputTerminal(responses) } } diff --git a/internal/client/questions.go b/internal/client/questions.go new file mode 100644 index 0000000..a14baba --- /dev/null +++ b/internal/client/questions.go @@ -0,0 +1,54 @@ +package client + +import ( + "strings" + + "github.com/miekg/dns" +) + +// LoadFallbacks sets fallbacks for options +// that are not specified by the user but necessary +// for the resolver. +func (hub *Client) 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") + } +} + +// PrepareQuestions takes a list of query names, query types and query classes +// and prepare a question for each combination of the above. +func (hub *Client) PrepareQuestions() { + for _, n := range hub.QueryFlags.QNames { + for _, t := range hub.QueryFlags.QTypes { + for _, c := range hub.QueryFlags.QClasses { + hub.Questions = append(hub.Questions, dns.Question{ + Name: n, + Qtype: dns.StringToType[strings.ToUpper(t)], + Qclass: dns.StringToClass[strings.ToUpper(c)], + }) + } + } + } +} + +// ReverseLookup is used to perform a reverse DNS Lookup +// using an IPv4 or IPv6 address. +// Query Type is set to PTR, Query Class is set to IN. +// Query Names must be formatted in in-addr.arpa. or ip6.arpa format. +func (hub *Client) ReverseLookup() { + hub.QueryFlags.QTypes = []string{"PTR"} + hub.QueryFlags.QClasses = []string{"IN"} + formattedNames := make([]string, 0, len(hub.QueryFlags.QNames)) + + for _, n := range hub.QueryFlags.QNames { + addr, err := dns.ReverseAddr(n) + if err != nil { + hub.Log.WithError(err).Fatal("error formatting address") + } + formattedNames = append(formattedNames, addr) + } + hub.QueryFlags.QNames = formattedNames +} diff --git a/pkg/config/config.go b/internal/config/config.go similarity index 100% rename from pkg/config/config.go rename to internal/config/config.go diff --git a/pkg/config/config_unix.go b/internal/config/config_unix.go similarity index 100% rename from pkg/config/config_unix.go rename to internal/config/config_unix.go diff --git a/pkg/config/config_windows.go b/internal/config/config_windows.go similarity index 100% rename from pkg/config/config_windows.go rename to internal/config/config_windows.go diff --git a/pkg/models/models.go b/internal/models/models.go similarity index 100% rename from pkg/models/models.go rename to internal/models/models.go diff --git a/pkg/resolvers/classic.go b/internal/resolvers/classic.go similarity index 98% rename from pkg/resolvers/classic.go rename to internal/resolvers/classic.go index 3732aa7..fc642cf 100644 --- a/pkg/resolvers/classic.go +++ b/internal/resolvers/classic.go @@ -72,6 +72,8 @@ func (r *ClassicResolver) Lookup(question dns.Question) (Response, error) { "nameserver": r.server, }).Debug("attempting to resolve") + r.resolverOptions.Logger.Debug("abc") + // Since the library doesn't include tcp.Dial time, // it's better to not rely on `rtt` provided here and calculate it ourselves. now := time.Now() diff --git a/pkg/resolvers/dnscrypt.go b/internal/resolvers/dnscrypt.go similarity index 100% rename from pkg/resolvers/dnscrypt.go rename to internal/resolvers/dnscrypt.go diff --git a/pkg/resolvers/doh.go b/internal/resolvers/doh.go similarity index 100% rename from pkg/resolvers/doh.go rename to internal/resolvers/doh.go diff --git a/pkg/resolvers/doq.go b/internal/resolvers/doq.go similarity index 100% rename from pkg/resolvers/doq.go rename to internal/resolvers/doq.go diff --git a/pkg/resolvers/resolver.go b/internal/resolvers/resolver.go similarity index 100% rename from pkg/resolvers/resolver.go rename to internal/resolvers/resolver.go diff --git a/pkg/resolvers/utils.go b/internal/resolvers/utils.go similarity index 100% rename from pkg/resolvers/utils.go rename to internal/resolvers/utils.go diff --git a/pkg/utils/logger.go b/pkg/utils/logger.go deleted file mode 100644 index 0f6e28d..0000000 --- a/pkg/utils/logger.go +++ /dev/null @@ -1,12 +0,0 @@ -package utils - -import ( - "github.com/mr-karan/logf" -) - -// InitLogger initializes logger. -func InitLogger() *logf.Logger { - logger := logf.New() - logger.SetColorOutput(true) - return logger -}