unfinished mess

cli
Karan Sharma 2022-07-06 21:52:59 +05:30
parent 6c3b17ba0d
commit 2e2e3b1ec8
26 changed files with 364 additions and 171 deletions

View File

@ -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()

View File

@ -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))

View File

@ -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"
)

View File

@ -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)
}

10
cmd/doggo/init.go 100644
View File

@ -0,0 +1,10 @@
package main
import "github.com/mr-karan/logf"
func initLogger() *logf.Logger {
logf := logf.New()
logf.SetColorOutput(true)
return logf
}

77
cmd/doggo/main.go 100644
View File

@ -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
}

4
go.mod
View File

@ -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

12
go.sum
View File

@ -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=

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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()

View File

@ -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
}