diff --git a/Makefile b/Makefile index d9bb0e7..6ecb350 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,6 @@ GO := go GOFLAGS := -ldflags '-s -w' PREFIX := /usr/local BINPATH = $(PREFIX)/bin -export CGO_ENABLED=1 -export CC=gcc .PHONY: clean doc @@ -18,7 +16,7 @@ doc: scdoc < doc/awl.1.md > doc/awl.1 test: - $(GO) test -race -v ./... -cover + $(GO) test -v ./... -cover fmt: gofmt -w -s . @@ -30,6 +28,12 @@ lint: fmt vet install: awl install awl $(BINPATH) + +install_doc: doc + install doc/awl.1 $(PREFIX)/share/man/man1 + @echo "" + @echo "Make sure to update your manual database" + @echo "'sudo mandb' on Debian/Ubuntu" clean: $(GO) clean \ No newline at end of file diff --git a/cli/cli.go b/cli/cli.go index c6309f6..be1e509 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -6,16 +6,18 @@ import ( "fmt" "os" "runtime" + "strings" - "git.froth.zone/sam/awl/internal/structs" + "git.froth.zone/sam/awl/internal/helpers" "git.froth.zone/sam/awl/util" "github.com/miekg/dns" flag "github.com/stefansundin/go-zflag" ) -// Parse the arguments passed into awl +// Parse the arguments passed into awl. func ParseCLI(version string) (Options, error) { + flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) flag.Usage = func() { fmt.Println(`awl - drill, writ small @@ -24,20 +26,19 @@ func ParseCLI(version string) (Options, error) { domain, IP address, phone number defaults to A - Arguments may be in any order, including flags. - Dig-like +[no]commands are also supported, see dig(1) or dig -h + Arguments may be in any order, including flags. + Dig-like +[no]commands are also supported, see dig(1) or dig -h Options:`) flag.PrintDefaults() - os.Exit(0) } // CLI flag var ( - port = flag.Int("port", 0, "`port` to make DNS query (default: 53 for UDP/TCP, 853 for TLS/QUIC", flag.OptShorthand('p'), flag.OptDisablePrintDefault(true)) - query = flag.String("query", ".", "domain name to query", flag.OptShorthand('q')) - class = flag.String("class", "IN", "DNS class to query", flag.OptShorthand('c')) - qType = flag.String("qType", "A", "type to query", flag.OptShorthand('t')) + port = flag.Int("port", 0, "`port` to make DNS query (default: 53 for UDP/TCP, 853 for TLS/QUIC)", flag.OptShorthand('p'), flag.OptDisablePrintDefault(true)) + query = flag.String("query", "", "domain name to `query` (default: .)", flag.OptShorthand('q')) + class = flag.String("class", "IN", "DNS `class` to query", flag.OptShorthand('c')) + qType = flag.String("qType", "", "`type` to query (default: A)", flag.OptShorthand('t')) ipv4 = flag.Bool("4", false, "force IPv4", flag.OptShorthandStr("4")) ipv6 = flag.Bool("6", false, "force IPv6", flag.OptShorthand('6')) @@ -54,9 +55,9 @@ func ParseCLI(version string) (Options, error) { aa = flag.Bool("aa", false, "set/unset AA (Authoratative Answer) flag (default: not set)") ad = flag.Bool("ad", false, "set/unset AD (Authenticated Data) flag (default: not set)") cd = flag.Bool("cd", false, "set/unset CD (Checking Disabled) flag (default: not set)") - qr = flag.Bool("qr", false, "set/unset QR (QueRy) flag (default: set)", flag.OptDisablePrintDefault(true)) + qr = flag.Bool("qr", false, "set/unset QR (QueRy) flag (default: not set)") rd = flag.Bool("rd", true, "set/unset RD (Recursion Desired) flag (default: set)", flag.OptDisablePrintDefault(true)) - ra = flag.Bool("ra", false, "set/unset RA (Recursion Available) flag (default: set)", flag.OptDisablePrintDefault(true)) + ra = flag.Bool("ra", false, "set/unset RA (Recursion Available) flag (default: not set)") tc = flag.Bool("tc", false, "set/unset TC (TrunCated) flag (default: not set)") z = flag.Bool("z", false, "set/unset Z (Zero) flag (default: not set)", flag.OptShorthand('z')) @@ -73,11 +74,13 @@ func ParseCLI(version string) (Options, error) { flag.CommandLine.SortFlags = false // Parse the flags - flag.Parse() + err := flag.CommandLine.Parse(os.Args[1:]) + if err != nil { + return Options{Logger: util.InitLogger(*verbosity)}, err + } opts := Options{ Logger: util.InitLogger(*verbosity), - Class: dns.StringToClass[*class], Port: *port, IPv4: *ipv4, IPv6: *ipv6, @@ -100,22 +103,24 @@ func ParseCLI(version string) (Options, error) { JSON: *json, XML: *xml, YAML: *yaml, - Request: structs.Request{ - Type: dns.StringToType[*qType], - Name: *query, + Request: helpers.Request{ + Type: dns.StringToType[strings.ToUpper(*qType)], + Class: dns.StringToClass[strings.ToUpper(*class)], + Name: *query, }, } if *versionFlag { fmt.Printf("awl version %s, built with %s\n", version, runtime.Version()) - os.Exit(0) + return opts, ErrNotError } // Parse all the arguments that don't start with - or -- // This includes the dig-style (+) options - err := ParseMiscArgs(flag.Args(), &opts) + err = ParseMiscArgs(flag.Args(), &opts) if err != nil { - opts.Logger.Fatal(err) + opts.Logger.Error(err) + return opts, err } if opts.Port == 0 { diff --git a/cli/cli_test.go b/cli/cli_test.go index 7ac5666..36ec6be 100644 --- a/cli/cli_test.go +++ b/cli/cli_test.go @@ -2,44 +2,95 @@ package cli_test -// TODO: readd these +import ( + "os" + "testing" -// import ( -// "os" -// "testing" + "git.froth.zone/sam/awl/cli" + "gotest.tools/v3/assert" +) -// "git.froth.zone/sam/awl/internal/structs" -// "git.froth.zone/sam/awl/query" +func TestEmpty(t *testing.T) { + old := os.Args + os.Args = []string{""} + opts, err := cli.ParseCLI("TEST") + assert.NilError(t, err) + assert.Assert(t, opts != cli.Options{}) + os.Args = old +} -// "github.com/miekg/dns" -// "github.com/stretchr/testify/assert" -// "github.com/stretchr/testify/require" -// ) +func TestInvalidFlag(t *testing.T) { + old := os.Args + os.Args = []string{"awl", "--treebug"} + _, err := cli.ParseCLI("TEST") + assert.ErrorContains(t, err, "unknown flag") + os.Args = old +} -// func TestApp(t *testing.T) { -// t.Parallel() -// app := prepareCLI() -// // What more can even be done lmao -// require.NotNil(t, app) +func TestInvalidDig(t *testing.T) { + old := os.Args + os.Args = []string{"awl", "+a"} + _, err := cli.ParseCLI("TEST") + assert.ErrorContains(t, err, "dig: unknown flag given") + os.Args = old +} + +// func TestArgParse(t *testing.T) { +// tests := []struct { +// in []string +// want helpers.Request +// }{ +// { +// []string{"@::1", "localhost", "AAAA"}, +// helpers.Request{Server: "::1", Type: dns.TypeAAAA, Name: "localhost"}, +// }, +// { +// []string{"@1.0.0.1", "google.com"}, +// helpers.Request{Server: "1.0.0.1", Type: dns.TypeA, Name: "google.com"}, +// }, +// { +// []string{"@8.8.4.4"}, +// helpers.Request{Server: "8.8.4.4", Type: dns.TypeNS, Name: "."}, +// }, +// } +// for _, test := range tests { +// old := os.Args +// act, err := cli.ParseCLI(test.in) +// assert.Nil(t, err) +// assert.Equal(t, test.want, act) +// } // } +func FuzzFlags(f *testing.F) { + testcases := []string{"git.froth.zone", "", "!12345", "google.com.edu.org.fr"} + for _, tc := range testcases { + f.Add(tc) + } + + f.Fuzz(func(t *testing.T, orig string) { + os.Args = []string{orig} + //nolint:errcheck // Only make sure the program does not crash + cli.ParseCLI("TEST") + }) +} + // func TestArgParse(t *testing.T) { // t.Parallel() // tests := []struct { // in []string -// want structs.Request +// want helpers.Request // }{ // { // []string{"@::1", "localhost", "AAAA"}, -// structs.Request{Server: "::1", Type: dns.TypeAAAA, Name: "localhost"}, +// helpers.Request{Server: "::1", Type: dns.TypeAAAA, Name: "localhost"}, // }, // { // []string{"@1.0.0.1", "google.com"}, -// structs.Request{Server: "1.0.0.1", Type: dns.TypeA, Name: "google.com"}, +// helpers.Request{Server: "1.0.0.1", Type: dns.TypeA, Name: "google.com"}, // }, // { // []string{"@8.8.4.4"}, -// structs.Request{Server: "8.8.4.4", Type: dns.TypeNS, Name: "."}, +// helpers.Request{Server: "8.8.4.4", Type: dns.TypeNS, Name: "."}, // }, // } // for _, test := range tests { diff --git a/cli/dig.go b/cli/dig.go index 938eff5..e697ed9 100644 --- a/cli/dig.go +++ b/cli/dig.go @@ -7,27 +7,27 @@ import ( "strings" ) -// Parse dig-like commands and set the options as such +// Parse dig-like commands and set the options as such. func ParseDig(arg string, opts *Options) error { // returns true if the flag starts with a no isNo := !strings.HasPrefix(arg, "no") switch arg { // Set DNS query flags - case "aaflag", "aaonly", "noaaflag", "noaaonly": + case "aa", "aaflag", "aaonly", "noaa", "noaaflag", "noaaonly": opts.AA = isNo - case "adflag", "noadflag": + case "ad", "adflag", "noad", "noadflag": opts.AD = isNo - case "cdflag", "nocdflag": + case "cd", "cdflag", "nocd", "nocdflag": opts.CD = isNo - case "qrflag", "noqrflag": + case "qr", "qrflag", "noqr", "noqrflag": opts.QR = isNo - case "raflag", "noraflag": + case "ra", "raflag", "nora", "noraflag": opts.RA = isNo - case "rdflag", "recurse", "nordflag", "norecurse": + case "rd", "rdflag", "recurse", "nord", "nordflag", "norecurse": opts.RD = isNo - case "tcflag", "notcflag": + case "tc", "tcflag", "notc", "notcflag": opts.TC = isNo - case "zflag", "nozflag": + case "z", "zflag", "noz", "nozflag": opts.Z = isNo // End DNS query flags @@ -36,8 +36,7 @@ func ParseDig(arg string, opts *Options) error { case "tcp", "vc", "notcp", "novc": opts.TCP = isNo case "ignore", "noignore": - // Invert (ignore truncation when true) - opts.Truncate = !isNo + opts.Truncate = isNo // Formatting case "short", "noshort": @@ -51,7 +50,7 @@ func ParseDig(arg string, opts *Options) error { // End formatting default: - return fmt.Errorf("dig: Unknown flag given") + return fmt.Errorf("dig: unknown flag given") } return nil } diff --git a/cli/dig_test.go b/cli/dig_test.go new file mode 100644 index 0000000..8d1292a --- /dev/null +++ b/cli/dig_test.go @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package cli_test + +import ( + "testing" + + "git.froth.zone/sam/awl/cli" + "gotest.tools/v3/assert" +) + +func FuzzDig(f *testing.F) { + f.Log("ParseDig Fuzzing") + seeds := []string{ + "aaflag", "aaonly", "noaaflag", "noaaonly", + "adflag", "noadflag", + "cdflag", "nocdflag", + "qrflag", "noqrflag", + "raflag", "noraflag", + "rdflag", "recurse", "nordflag", "norecurse", + "tcflag", "notcflag", + "zflag", "nozflag", + "dnssec", "nodnssec", + "tcp", "vc", "notcp", "novc", + "ignore", "noignore", + "short", "noshort", + "json", "nojson", + "xml", "noxml", + "yaml", "noyaml", + "invalid", + } + for _, tc := range seeds { + f.Add(tc) + } + + f.Fuzz(func(t *testing.T, orig string) { + opts := new(cli.Options) + err := cli.ParseDig(orig, opts) + if err != nil { + assert.ErrorContains(t, err, "unknown flag given") + } + }) +} diff --git a/cli/misc.go b/cli/misc.go index 693ecda..c334e12 100644 --- a/cli/misc.go +++ b/cli/misc.go @@ -8,14 +8,13 @@ import ( "strings" "git.froth.zone/sam/awl/conf" - "git.froth.zone/sam/awl/internal/structs" "git.froth.zone/sam/awl/util" "github.com/miekg/dns" "golang.org/x/net/idna" ) -// Parse the wildcard arguments, drill style +// Parse the wildcard arguments, drill style. func ParseMiscArgs(args []string, opts *Options) error { var err error @@ -26,13 +25,13 @@ func ParseMiscArgs(args []string, opts *Options) error { case strings.HasPrefix(arg, "@"): opts.Request.Server = arg[1:] case strings.Contains(arg, "."): - opts.Query, err = idna.ToASCII(arg) + opts.Request.Name, err = idna.ToASCII(arg) if err != nil { return err } case ok: // If it's a DNS request, it's a DNS request (obviously) - opts.Type = r + opts.Request.Type = r case strings.HasPrefix(arg, "+"): // Dig-style +queries err = ParseDig(strings.ToLower(arg[1:]), opts) @@ -40,26 +39,26 @@ func ParseMiscArgs(args []string, opts *Options) error { return err } default: - //else, assume it's a name - opts.Query, err = idna.ToASCII(arg) + + opts.Request.Name, err = idna.ToASCII(arg) if err != nil { return err } - } } // If nothing was set, set a default - if opts.Query == "" { - opts.Query = "." - if opts.Type == 0 { - opts.Type = dns.StringToType["NS"] + if opts.Request.Name == "" { + opts.Request.Name = "." + if opts.Request.Type == 0 { + opts.Request.Type = dns.StringToType["NS"] } } else { - if opts.Type == 0 { - opts.Type = dns.StringToType["A"] + if opts.Request.Type == 0 { + opts.Request.Type = dns.StringToType["A"] } } + // if opts.Request.Server == "" { switch { case opts.TLS: @@ -97,29 +96,17 @@ func ParseMiscArgs(args []string, opts *Options) error { // Make reverse adresses proper addresses if opts.Reverse { if dns.TypeToString[opts.Request.Type] == "A" { - opts.Type = dns.StringToType["PTR"] + opts.Request.Type = dns.StringToType["PTR"] } - opts.Query, err = util.ReverseDNS(opts.Query, opts.Request.Type) + opts.Request.Name, err = util.ReverseDNS(opts.Request.Name, opts.Request.Type) if err != nil { return err } } - if opts.Class == 0 { - opts.Class = dns.StringToClass["IN"] - } - // if the domain is not canonical, make it canonical - if !strings.HasSuffix(opts.Query, ".") { - opts.Query = fmt.Sprintf("%s.", opts.Query) + if !strings.HasSuffix(opts.Request.Name, ".") { + opts.Request.Name = fmt.Sprintf("%s.", opts.Request.Name) } - - opts.Request = structs.Request{ - Server: opts.Request.Server, - Type: opts.Type, - Class: opts.Class, - Name: opts.Query, - } - return nil } diff --git a/cli/misc_test.go b/cli/misc_test.go new file mode 100644 index 0000000..bbed34b --- /dev/null +++ b/cli/misc_test.go @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package cli_test + +import ( + "testing" + + "git.froth.zone/sam/awl/cli" + + "github.com/miekg/dns" + "gotest.tools/v3/assert" +) + +func TestParseArgs(t *testing.T) { + t.Parallel() + args := []string{ + "go.dev", + "AAAA", + "@1.1.1.1", + "+ignore", + } + opts := new(cli.Options) + err := cli.ParseMiscArgs(args, opts) + assert.NilError(t, err) + assert.Equal(t, opts.Request.Name, "go.dev.") + assert.Equal(t, opts.Request.Type, dns.StringToType["AAAA"]) + assert.Equal(t, opts.Request.Server, "1.1.1.1") + assert.Equal(t, opts.Truncate, true) +} + +func TestParseNoInput(t *testing.T) { + t.Parallel() + args := []string{} + opts := new(cli.Options) + err := cli.ParseMiscArgs(args, opts) + assert.NilError(t, err) + assert.Equal(t, opts.Request.Name, ".") + assert.Equal(t, opts.Request.Type, dns.StringToType["NS"]) +} + +func TestParseA(t *testing.T) { + t.Parallel() + args := []string{ + "golang.org.", + } + opts := new(cli.Options) + err := cli.ParseMiscArgs(args, opts) + assert.NilError(t, err) + assert.Equal(t, opts.Request.Name, "golang.org.") + assert.Equal(t, opts.Request.Type, dns.StringToType["A"]) +} + +func TestParsePTR(t *testing.T) { + t.Parallel() + args := []string{"8.8.8.8"} + opts := new(cli.Options) + opts.Reverse = true + err := cli.ParseMiscArgs(args, opts) + assert.NilError(t, err) + assert.Equal(t, opts.Request.Type, dns.StringToType["PTR"]) +} + +func TestDefaultServer(t *testing.T) { + t.Parallel() + tests := []struct { + in string + want string + }{ + {"TLS", "dns.google"}, + {"HTTPS", "https://dns.cloudflare.com/dns-query"}, + {"QUIC", "dns.adguard.com"}, + } + for _, test := range tests { + test := test + t.Run(test.in, func(t *testing.T) { + t.Parallel() + args := []string{} + opts := new(cli.Options) + switch test.in { + case "TLS": + opts.TLS = true + case "HTTPS": + opts.HTTPS = true + case "QUIC": + opts.QUIC = true + } + err := cli.ParseMiscArgs(args, opts) + assert.NilError(t, err) + assert.Equal(t, opts.Request.Server, test.want) + }) + } +} + +func FuzzParseArgs(f *testing.F) { + cases := []string{ + "go.dev", + "AAAA", + "@1.1.1.1", + "+ignore", + "e", + } + for _, tc := range cases { + f.Add(tc) + } + f.Fuzz(func(t *testing.T, arg string) { + args := []string{arg} + opts := new(cli.Options) + //nolint:errcheck // Only make sure the program does not crash + cli.ParseMiscArgs(args, opts) + }) +} diff --git a/cli/options.go b/cli/options.go index c1891c1..4f07778 100644 --- a/cli/options.go +++ b/cli/options.go @@ -3,17 +3,16 @@ package cli import ( - "git.froth.zone/sam/awl/internal/structs" + "errors" + + "git.froth.zone/sam/awl/internal/helpers" "git.froth.zone/sam/awl/logawl" ) -// CLI options structure +// CLI options structure. type Options struct { Logger *logawl.Logger // Logger Port int // DNS port - Query string // DNS Query - Class uint16 // DNS Class - Type uint16 // DNS Type IPv4 bool // Force IPv4 IPv6 bool // Force IPv6 DNSSEC bool // Enable DNSSEC @@ -32,10 +31,13 @@ type Options struct { Z bool // Set Z (Zero) Reverse bool // Make reverse query Verbosity int // Set logawl verbosity + HumanTTL bool // Make TTL human readable Short bool // Short output JSON bool // Outout as JSON XML bool // Output as XML YAML bool // Output at YAML - Request structs.Request + Request helpers.Request } + +var ErrNotError = errors.New("not an error") diff --git a/conf/plan9.go b/conf/plan9.go index cb507fb..2819c92 100644 --- a/conf/plan9.go +++ b/conf/plan9.go @@ -13,7 +13,7 @@ import ( // Yoink it and use it. // // See ndb(7). -func getPlan9Config(str string) (*dns.ClientConfig, error) { +func GetPlan9Config(str string) (*dns.ClientConfig, error) { str = strings.ReplaceAll(str, "\n", "") spl := strings.FieldsFunc(str, splitChars) var servers []string @@ -34,7 +34,7 @@ func getPlan9Config(str string) (*dns.ClientConfig, error) { }, nil } -// Split the string at either space or tabs +// Split the string at either space or tabs. func splitChars(r rune) bool { return r == ' ' || r == '\t' } diff --git a/conf/plan9_test.go b/conf/plan9_test.go index 573906d..a5123fc 100644 --- a/conf/plan9_test.go +++ b/conf/plan9_test.go @@ -1,11 +1,12 @@ // SPDX-License-Identifier: BSD-3-Clause -package conf +package conf_test import ( "testing" - "github.com/stretchr/testify/assert" + "git.froth.zone/sam/awl/conf" + "gotest.tools/v3/assert" ) func TestGetPlan9Config(t *testing.T) { @@ -28,9 +29,14 @@ func TestGetPlan9Config(t *testing.T) { } for _, ndb := range ndbs { - act, err := getPlan9Config(ndb.in) - assert.Nil(t, err) - assert.Equal(t, ndb.want, act.Servers[0]) + // Go is a little quirky + ndb := ndb + t.Run(ndb.want, func(t *testing.T) { + t.Parallel() + act, err := conf.GetPlan9Config(ndb.in) + assert.NilError(t, err) + assert.Equal(t, ndb.want, act.Servers[0]) + }) } invalid := `sys = spindle @@ -39,8 +45,7 @@ func TestGetPlan9Config(t *testing.T) { ip=135.104.117.32 ether=080069020677 proto=il` - act, err := getPlan9Config(invalid) + act, err := conf.GetPlan9Config(invalid) assert.ErrorContains(t, err, "no DNS servers found") - assert.Nil(t, act) - + assert.Assert(t, act == nil) } diff --git a/conf/notwin.go b/conf/unix.go similarity index 88% rename from conf/notwin.go rename to conf/unix.go index 7f8aa7a..12eb49f 100644 --- a/conf/notwin.go +++ b/conf/unix.go @@ -1,6 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause //go:build !windows -// +build !windows package conf @@ -18,7 +17,7 @@ func GetDNSConfig() (*dns.ClientConfig, error) { if err != nil { return nil, err } - return getPlan9Config(string(dat)) + return GetPlan9Config(string(dat)) } else { return dns.ClientConfigFromFile("/etc/resolv.conf") } diff --git a/conf/unix_test.go b/conf/unix_test.go new file mode 100644 index 0000000..9899c3a --- /dev/null +++ b/conf/unix_test.go @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: BSD-3-Clause +//go:build !windows + +package conf_test + +import ( + "runtime" + "testing" + + "git.froth.zone/sam/awl/conf" + "gotest.tools/v3/assert" +) + +func TestNonWinConfig(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Not running Windows, skipping") + } + conf, err := conf.GetDNSConfig() + assert.NilError(t, err) + assert.Assert(t, len(conf.Servers) != 0) +} diff --git a/conf/win_test.go b/conf/win_test.go new file mode 100644 index 0000000..0b1531e --- /dev/null +++ b/conf/win_test.go @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: BSD-3-Clause +//go:build windows + +package conf_test + +import ( + "runtime" + "testing" + + "git.froth.zone/sam/awl/conf" + "gotest.tools/v3/assert" +) + +func TestWinConfig(t *testing.T) { + if runtime.GOOS != "windows" { + t.Skip("Not running Windows, skipping") + } + conf, err := conf.GetDNSConfig() + assert.NilError(t, err) + assert.Assert(t, len(conf.Servers) != 0) +} diff --git a/doc/awl.1 b/doc/awl.1 index 18cbefa..597ff69 100644 --- a/doc/awl.1 +++ b/doc/awl.1 @@ -5,7 +5,7 @@ .nh .ad l .\" Begin generated content: -.TH "AWL" "1" "2022-07-20" +.TH "awl" "1" "2022-07-21" .PP .SH NAME awl - drill, writ small @@ -23,23 +23,27 @@ where .PP .SH DESCRIPTION .PP -\fBawl\fR (\fBa\fRwls \fBw\fRant \fBl\fRicorice) is a simple tool designed to make DNS queries, much like the venerable \fIdig\fR(1).\& -An awl is a tool used to make small holes, typically used in tannery (leatherworking).\& +\fBawl\fR (\fBa\fRwls \fBw\fRant \fBl\fRicorice) is a simple tool designed to make DNS queries, much +like the venerable \fIdig\fR(1).\& An awl is a tool used to make small holes, typically +used in tannery (leatherworking).\& .PP -Written in Go, \fBawl\fR is designed to be a more "modern" version of \fIdrill\fR(1) by including some more RFCs and output options.\& -\fBawl\fR is still heavily Work-In-Progress so some features may get added or removed.\& +Written in Go, \fBawl\fR is designed to be a more "modern" version of \fIdrill\fR(1) by +including some more RFCs and output options.\& \fBawl\fR is still heavily +Work-In-Progress so some features may get added or removed.\& .PP .SH OPTIONS .RS 4 -\fB-D\fR, \fB--dnssec\fR +Dig-like queries are supported, see dig(1) +.PP +\fB-D\fR, \fB--dnssec\fR, \fB+dnssec\fR .br Enable DNSSEC.\& This needs to be manually enabled.\& .PP -\fB--debug\fR +\fB-v\fR \fIvalue\fR .br - Enable debug logging (currently WIP).\& + Set verbosity (currently WIP) .PP -\fB-v\fR +\fB-V\fR .br Print the version and exit.\& .PP @@ -76,27 +80,35 @@ Written in Go, \fBawl\fR is designed to be a more "modern" version of \fIdrill\f .PD .PP .RE -\fB--no-truncate\fR +\fB-q\fR, \fB--query\fR \fIdomain\fR +.br + Domain to query (eg.\& example.\&com) +.PP +\fB-c\fR, \fB--class\fR \fIclass\fR +.br + DNS class to query (eg.\& IN, CH) +.PP +\fB-t\fR, \fB--qType\fR \fItype\fR +.br + DNS type to query (eg.\& A, NS) +.PP +\fB--no-truncate\fR, \fB+ignore\fR .br Ignore UDP truncation (by default, awl \fIretries with TCP\fR) .PP -\fB-t\fR, \fB--tcp\fR +\fB-t\fR, \fB--tcp\fR, \fB+tcp\fR, \fB+vc\fR .br Use TCP for the query (see \fIRFC 7766\fR) .PP -\fB-u\fR, \fB--udp\fR -.br - Use UDP for the query (default) -.PP -\fB-T\fR, \fB--tls\fR +\fB-T\fR, \fB--tls\fR, \fB+tls\fR .br Use DNS-over-TLS, implies \fB-t\fR (see \fIRFC 7858\fR) .PP -\fB-H\fR.\& \fB--https\fR +\fB-H\fR.\& \fB--https\fR, \fB+https\fR .br Use DNS-over-HTTPS (see \fIRFC 8484\fR) .PP -\fB-Q\fR.\& \fB--quic\fR +\fB-Q\fR.\& \fB--quic\fR, \fB+quic\fR .br Use DNS-over-QUIC (see \fIRFC 9250\fR) .PP @@ -110,59 +122,80 @@ Written in Go, \fBawl\fR is designed to be a more "modern" version of \fIdrill\f .SS DNS Flags .PP .RS 4 -\fB--aa\fR +\fB--aa=[false]\fR, \fB+[no]aaflag\fR .br - \fISET\fR Authoritative Answer (default: unset) + (Set, Unset) AA (Authoritative Answer) flag .PP -\fB--ad\fR +\fB--ad=[false]\fR, \fB+[no]adflag\fR .br - \fISET\fR Authenticated Data (default: unset) + (Set, Unset) AD (Authenticated Data) flag .PP -\fB--tc\fR +\fB--tc=[false]\fR, \fB+[no]tcflag\fR .br - \fISET\fR TC (TrunCated) flag (default: not set) + (Set, Unset) TC (TrunCated) flag .PP -\fB-z\fR +\fB-z=[false]\fR, \fB+[no]zflag\fR .br - \fISET\fR Z (Zero) flag (default: not set) + (Set, Unset) Z (Zero) flag .PP -\fB--cd\fR +\fB--cd=[false]\fR, \fB+[no]cdflag\fR .br - \fISET\fR CD (Checking Disabled) flag (default: not set) + (Set, Unset) CD (Checking Disabled) flag .PP -\fB--no-qr\fR +\fB--qr=[false]\fR, \fB+[no]qrflag\fR .br - \fIUNSET\fR QR (QueRy) flag (default: set) + (Set, Unset) QR (QueRy) flag .PP -\fB--no-rd\fR +\fB--rd=[true]\fR, \fB+[no]rdflag\fR .br - \fIUNSET\fR RD (Recursion Desired) flag (default: set) + (Set, Unset) RD (Recursion Desired) flag .PP -\fB--no-ra\fR +\fB--ra=[false]\fR, \fB+[no]raflag\fR .br - \fIUNSET\fR RA (Recursion Available) flag (default: set) + (Set, Unset) RA (Recursion Available) flag .PP .RE .SS Output Options .RS 4 -\fB-j\fR, \fB--json\fR +\fB-j\fR, \fB--json\fR, \fB+json\fR .br Print the query results as JSON.\& .PP -\fB-X\fR, \fB--xml\fR +\fB-X\fR, \fB--xml\fR, \fB+xml\fR .br Print the query results as XML.\& .PP -\fB-y\fR, \fB--yaml\fR +\fB-y\fR, \fB--yaml\fR, \fB+yaml\fR .br Print the query results as YAML.\& .PP -\fB-s\fR, \fB--short\fR +\fB-s\fR, \fB--short\fR, \fB+short\fR .br Print just the results.\& -.br - Equivalent to \fBdig +short\fR .PP .RE +.SH EXAMPLES +.nf +.RS 4 +awl grumbulon\&.xyz -j +cd +.fi +.RE +Run a query of your local resolver for the A records of grumbulon.\&xyz, print +them as JSON and disable DNSSEC verification.\& +.PP +.nf +.RS 4 +awl +short example\&.com AAAA @1\&.1\&.1\&.1 +.fi +.RE +Query 1.\&1.\&1.\&1 for the AAAA records of example.\&com, print just the answers +.PP +.nf +.RS 4 +awl -xT PTR 8\&.8\&.4\&.4 @dns\&.google +.fi +.RE +Query dns.\&google over TLS for the PTR record to the IP address 8.\&8.\&4.\&4 +.PP .SH SEE ALSO \fIdrill\fR(1), \fIdig\fR(1), the many DNS RFCs \ No newline at end of file diff --git a/doc/awl.1.md b/doc/awl.1.md index 46f3125..e6d3126 100644 --- a/doc/awl.1.md +++ b/doc/awl.1.md @@ -1,4 +1,4 @@ -AWL(1) +awl(1) # NAME awl - drill, writ small @@ -13,22 +13,24 @@ _type_ is the DNS resource type (*example: AAAA*) # DESCRIPTION -*awl* (*a*wls *w*ant *l*icorice) is a simple tool designed to make DNS queries, much like the venerable _dig_(1). -An awl is a tool used to make small holes, typically used in tannery (leatherworking). +*awl* (*a*wls *w*ant *l*icorice) is a simple tool designed to make DNS queries, much +like the venerable _dig_(1). An awl is a tool used to make small holes, typically +used in tannery (leatherworking). -Written in Go, *awl* is designed to be a more "modern" version of _drill_(1) by including some more RFCs and output options. -*awl* is still heavily Work-In-Progress so some features may get added or removed. +Written in Go, *awl* is designed to be a more "modern" version of _drill_(1) by +including some more RFCs and output options. *awl* is still heavily +Work-In-Progress so some features may get added or removed. # OPTIONS - _Dig-like queries are supported, see dig(1)_ + Dig-like queries are supported, see dig(1) - *-D*, *--dnssec*++ + *-D*, *--dnssec*, *+dnssec*++ Enable DNSSEC. This needs to be manually enabled. - *--debug*++ - Enable debug logging (currently WIP). + *-v* _value_++ + Set verbosity (currently WIP) - *-v*++ + *-V*++ Print the version and exit. *-h*++ @@ -49,22 +51,28 @@ _Default Ports_: - _853_ for *TLS* and *QUIC* - _443_ for *HTTPS* - *--no-truncate*++ + *-q*, *--query* _domain_++ + Domain to query (eg. example.com) + + *-c*, *--class* _class_++ + DNS class to query (eg. IN, CH) + + *-t*, *--qType* _type_++ + DNS type to query (eg. A, NS) + + *--no-truncate*, *+ignore*++ Ignore UDP truncation (by default, awl _retries with TCP_) - *-t*, *--tcp*++ + *-t*, *--tcp*, *+tcp*, *+vc*++ Use TCP for the query (see _RFC 7766_) - *-u*, *--udp*++ - Use UDP for the query (default) - - *-T*, *--tls*++ + *-T*, *--tls*, *+tls*++ Use DNS-over-TLS, implies *-t* (see _RFC 7858_) - *-H*. *--https*++ + *-H*. *--https*, *+https*++ Use DNS-over-HTTPS (see _RFC 8484_) - *-Q*. *--quic*++ + *-Q*. *--quic*, *+quic*++ Use DNS-over-QUIC (see _RFC 9250_) *-x*, *--reverse*++ @@ -73,43 +81,59 @@ _Default Ports_: ## DNS Flags - *--aa*++ - _SET_ Authoritative Answer (default: unset) + *--aa=[false]*, *+[no]aaflag*++ + (Set, Unset) AA (Authoritative Answer) flag - *--ad*++ - _SET_ Authenticated Data (default: unset) + *--ad=[false]*, *+[no]adflag*++ + (Set, Unset) AD (Authenticated Data) flag - *--tc*++ - _SET_ TC (TrunCated) flag (default: not set) + *--tc=[false]*, *+[no]tcflag*++ + (Set, Unset) TC (TrunCated) flag - *-z*++ - _SET_ Z (Zero) flag (default: not set) + *-z=[false]*, *+[no]zflag*++ + (Set, Unset) Z (Zero) flag - *--cd*++ - _SET_ CD (Checking Disabled) flag (default: not set) + *--cd=[false]*, *+[no]cdflag*++ + (Set, Unset) CD (Checking Disabled) flag - *--no-qr*++ - _UNSET_ QR (QueRy) flag (default: set) + *--qr=[false]*, *+[no]qrflag*++ + (Set, Unset) QR (QueRy) flag - *--no-rd*++ - _UNSET_ RD (Recursion Desired) flag (default: set) + *--rd=[true]*, *+[no]rdflag*++ + (Set, Unset) RD (Recursion Desired) flag - *--no-ra*++ - _UNSET_ RA (Recursion Available) flag (default: set) + *--ra=[false]*, *+[no]raflag*++ + (Set, Unset) RA (Recursion Available) flag ## Output Options - *-j*, *--json*++ + *-j*, *--json*, *+json*++ Print the query results as JSON. - *-X*, *--xml*++ + *-X*, *--xml*, *+xml*++ Print the query results as XML. - *-y*, *--yaml*++ + *-y*, *--yaml*, *+yaml*++ Print the query results as YAML. - *-s*, *--short*++ - Print just the results.++ - Equivalent to *dig +short* + *-s*, *--short*, *+short*++ + Print just the results. + +# EXAMPLES +``` +awl grumbulon.xyz -j +cd +``` +Run a query of your local resolver for the A records of grumbulon.xyz, print +them as JSON and disable DNSSEC verification. + +``` +awl +short example.com AAAA @1.1.1.1 +``` +Query 1.1.1.1 for the AAAA records of example.com, print just the answers + +``` +awl -xT PTR 8.8.4.4 @dns.google +``` +Query dns.google over TLS for the PTR record to the IP address 8.8.4.4 # SEE ALSO _drill_(1), _dig_(1), the many DNS RFCs \ No newline at end of file diff --git a/go.mod b/go.mod index 412acf0..af04dab 100644 --- a/go.mod +++ b/go.mod @@ -8,13 +8,12 @@ require ( github.com/stefansundin/go-zflag v1.1.1 golang.org/x/net v0.0.0-20220708220712-1185a9018129 gopkg.in/yaml.v2 v2.4.0 + gotest.tools/v3 v3.3.0 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/go-cmp v0.5.5 // indirect github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) require ( @@ -26,7 +25,7 @@ require ( github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/onsi/ginkgo v1.16.5 // indirect - github.com/stretchr/testify v1.8.0 + github.com/stretchr/testify v1.8.0 // 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/sys v0.0.0-20220715151400-c0bba94af5f8 diff --git a/go.sum b/go.sum index 5442a9b..a4ef21f 100644 --- a/go.sum +++ b/go.sum @@ -55,6 +55,7 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -145,6 +146,7 @@ github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYED github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stefansundin/go-zflag v1.1.1 h1:XabhzWS588bVvV1z1UctSa6i8zHkXc5W9otqtnDSHw8= github.com/stefansundin/go-zflag v1.1.1/go.mod h1:HXX5rABl1AoTcZ2jw+CqJ7R8irczaLquGNZlFabZooc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -226,6 +228,7 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -254,6 +257,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY= @@ -261,6 +265,7 @@ golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= @@ -303,6 +308,8 @@ 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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= +gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= 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/helpers/docs.go b/internal/helpers/docs.go new file mode 100644 index 0000000..945e5a6 --- /dev/null +++ b/internal/helpers/docs.go @@ -0,0 +1,4 @@ +/* +Useful structs used everywhere I couldn't find a better place to shove +*/ +package helpers diff --git a/internal/helpers/query.go b/internal/helpers/query.go new file mode 100644 index 0000000..a8247f8 --- /dev/null +++ b/internal/helpers/query.go @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package helpers + +import ( + "time" + + "github.com/miekg/dns" +) + +// The DNS response. +type Response struct { + DNS *dns.Msg // The full DNS response + RTT time.Duration `json:"rtt"` // The time it took to make the DNS query +} + +// A structure for a DNS query. +type Request struct { + Server string `json:"server"` // The server to make the DNS request from + Type uint16 `json:"request"` // The type of request + Class uint16 `json:"class"` // DNS Class + Name string `json:"name"` // The domain name to make a DNS request for +} diff --git a/internal/helpers/query_test.go b/internal/helpers/query_test.go new file mode 100644 index 0000000..f3f5da5 --- /dev/null +++ b/internal/helpers/query_test.go @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package helpers_test + +import ( + "testing" + + "gotest.tools/v3/assert" +) + +func TestNothing(t *testing.T) { + assert.Equal(t, 0, 0) +} diff --git a/logawl/docs.go b/logawl/docs.go index b67c5ea..ac20e97 100644 --- a/logawl/docs.go +++ b/logawl/docs.go @@ -20,7 +20,7 @@ because awl is a cli utility it writes directly to std err. // // Logger.SetLevel(3) // This allows you to change the default level (Info) and prevent log messages from being posted at higher verbosity levels -//for example if +// for example if // Logger.SetLevel(3) // is not called and you call // Logger.Debug() diff --git a/logawl/logawl.go b/logawl/logawl.go index f5c996e..5cbe445 100644 --- a/logawl/logawl.go +++ b/logawl/logawl.go @@ -9,32 +9,34 @@ import ( "sync/atomic" ) -type Level int32 -type Logger struct { - Mu sync.Mutex - Level Level - Prefix string - Out io.Writer - buf []byte - isDiscard int32 -} +type ( + Level int32 + Logger struct { + Mu sync.Mutex + Level Level + Prefix string + Out io.Writer + buf []byte + isDiscard int32 + } +) -// Stores whatever input value is in mem address of l.level +// Stores whatever input value is in mem address of l.level. func (l *Logger) SetLevel(level Level) { atomic.StoreInt32((*int32)(&l.Level), int32(level)) } -// Mostly nothing +// Mostly nothing. func (l *Logger) GetLevel() Level { return l.level() } -// Retrieves whatever was stored in mem address of l.level +// Retrieves whatever was stored in mem address of l.level. func (l *Logger) level() Level { return Level(atomic.LoadInt32((*int32)(&l.Level))) } -// Unmarshalls the int value of level for writing the header +// Unmarshalls the int value of level for writing the header. func (l *Logger) UnMarshalLevel(lv Level) (string, error) { switch lv { case 0: @@ -61,13 +63,13 @@ var AllLevels = []Level{ } const ( - // Fatal logs (will call exit(1)) + // Fatal logs (will call exit(1)). FatalLevel Level = iota - // Error logs + // Error logs. ErrorLevel - // What is going on level + // What is going on level. InfoLevel // Verbose log level. DebugLevel diff --git a/logawl/logger.go b/logawl/logger.go index 0a9939c..c2311c8 100644 --- a/logawl/logger.go +++ b/logawl/logger.go @@ -30,23 +30,27 @@ func (l *Logger) Println(level Level, v ...any) { case 0: err := l.Printer(0, fmt.Sprintln(v...)) //Fatal level if err != nil { + // TODO: Make this not a panic panic(err) } os.Exit(1) case 1: err := l.Printer(1, fmt.Sprintln(v...)) //Error level if err != nil { + // TODO: Make this not a panic panic(err) } os.Exit(2) case 2: err := l.Printer(2, fmt.Sprintln(v...)) //Info level if err != nil { + // TODO: Make this not a panic panic(err) } case 3: err := l.Printer(3, fmt.Sprintln(v...)) //Debug level if err != nil { + // TODO: Make this not a panic panic(err) } default: diff --git a/logawl/logging_test.go b/logawl/logging_test.go index 9b187dc..202feca 100644 --- a/logawl/logging_test.go +++ b/logawl/logging_test.go @@ -9,7 +9,7 @@ import ( "git.froth.zone/sam/awl/logawl" - "github.com/stretchr/testify/assert" + "gotest.tools/v3/assert" ) var logger = logawl.New() @@ -17,35 +17,29 @@ var logger = logawl.New() func TestLogawl(t *testing.T) { t.Parallel() - assert.Equal(t, logawl.Level(2), logger.Level) //cast 2 (int) to 2 (level) - - //Validate setting and getting levels from memory works for i := range logawl.AllLevels { logger.SetLevel(logawl.Level(i)) assert.Equal(t, logawl.Level(i), logger.GetLevel()) } - } func TestUnmarshalLevels(t *testing.T) { t.Parallel() m := make(map[int]string) var err error - //Fill map with unmarshalled level info + for i := range logawl.AllLevels { m[i], err = logger.UnMarshalLevel(logawl.Level(i)) - assert.Nil(t, err) + assert.NilError(t, err) } - //iterate over map and assert equal for i := range logawl.AllLevels { lv, err := logger.UnMarshalLevel(logawl.Level(i)) - assert.Nil(t, err) + assert.NilError(t, err) assert.Equal(t, m[i], lv) } lv, err := logger.UnMarshalLevel(logawl.Level(9001)) - assert.NotNil(t, err) assert.Equal(t, "", lv) assert.ErrorContains(t, err, "invalid log level choice") } @@ -79,13 +73,12 @@ func TestLogger(t *testing.T) { fn() } } - } func TestFmt(t *testing.T) { t.Parallel() ti := time.Now() test := []byte("test") - assert.NotNil(t, logger.FormatHeader(&test, ti, 0, logawl.Level(9001))) //make sure error is error + assert.ErrorContains(t, logger.FormatHeader(&test, ti, 0, 9001), "invalid log level") //make sure error is error } diff --git a/main.go b/main.go index 991be6b..a4d88ea 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ package main import ( "encoding/json" "encoding/xml" + "errors" "fmt" "os" "strings" @@ -21,6 +22,10 @@ var version = "DEV" func main() { opts, err := cli.ParseCLI(version) if err != nil { + // TODO: Make not ew + if errors.Is(err, cli.ErrNotError) || strings.Contains(err.Error(), "help requested") { + os.Exit(0) + } opts.Logger.Fatal(err) os.Exit(1) } @@ -67,5 +72,4 @@ func main() { } } } - } diff --git a/query/HTTPS.go b/query/HTTPS.go index df0bc6b..bb8d925 100644 --- a/query/HTTPS.go +++ b/query/HTTPS.go @@ -10,7 +10,7 @@ import ( "time" "git.froth.zone/sam/awl/cli" - "git.froth.zone/sam/awl/internal/structs" + "git.froth.zone/sam/awl/internal/helpers" "github.com/miekg/dns" ) @@ -19,18 +19,18 @@ type HTTPSResolver struct { opts cli.Options } -func (r *HTTPSResolver) LookUp(msg *dns.Msg) (structs.Response, error) { - var resp structs.Response +func (r *HTTPSResolver) LookUp(msg *dns.Msg) (helpers.Response, error) { + var resp helpers.Response httpR := &http.Client{} buf, err := msg.Pack() if err != nil { - return structs.Response{}, err + return helpers.Response{}, err } r.opts.Logger.Debug("making DoH request") // query := server + "?dns=" + base64.RawURLEncoding.EncodeToString(buf) req, err := http.NewRequest("POST", r.server, bytes.NewBuffer(buf)) if err != nil { - return structs.Response{}, fmt.Errorf("DoH: %s", err.Error()) + return helpers.Response{}, fmt.Errorf("DoH: %s", err.Error()) } req.Header.Set("Content-Type", "application/dns-message") req.Header.Set("Accept", "application/dns-message") @@ -40,23 +40,23 @@ func (r *HTTPSResolver) LookUp(msg *dns.Msg) (structs.Response, error) { resp.RTT = time.Since(now) if err != nil { - return structs.Response{}, fmt.Errorf("DoH HTTP request error: %s", err.Error()) + return helpers.Response{}, fmt.Errorf("DoH HTTP request error: %s", err.Error()) } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return structs.Response{}, fmt.Errorf("DoH server responded with HTTP %d", res.StatusCode) + return helpers.Response{}, fmt.Errorf("DoH server responded with HTTP %d", res.StatusCode) } fullRes, err := io.ReadAll(res.Body) if err != nil { - return structs.Response{}, fmt.Errorf("DoH body read error: %s", err.Error()) + return helpers.Response{}, fmt.Errorf("DoH body read error: %s", err.Error()) } resp.DNS = &dns.Msg{} r.opts.Logger.Debug("unpacking response") err = resp.DNS.Unpack(fullRes) if err != nil { - return structs.Response{}, fmt.Errorf("DoH dns message unpack error: %s", err.Error()) + return helpers.Response{}, fmt.Errorf("DoH dns message unpack error: %s", err.Error()) } return resp, nil diff --git a/query/HTTPS_test.go b/query/HTTPS_test.go index 8309554..bb0a5f4 100644 --- a/query/HTTPS_test.go +++ b/query/HTTPS_test.go @@ -8,12 +8,12 @@ import ( "testing" "git.froth.zone/sam/awl/cli" - "git.froth.zone/sam/awl/internal/structs" + "git.froth.zone/sam/awl/internal/helpers" "git.froth.zone/sam/awl/query" "git.froth.zone/sam/awl/util" "github.com/miekg/dns" - "github.com/stretchr/testify/assert" + "gotest.tools/v3/assert" ) func TestResolveHTTPS(t *testing.T) { @@ -23,9 +23,9 @@ func TestResolveHTTPS(t *testing.T) { HTTPS: true, Logger: util.InitLogger(0), } - testCase := structs.Request{Server: "dns9.quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone"} + testCase := helpers.Request{Server: "dns9.quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone"} resolver, err := query.LoadResolver(testCase.Server, opts) - assert.Nil(t, err) + assert.NilError(t, err) if !strings.HasPrefix(testCase.Server, "https://") { testCase.Server = "https://" + testCase.Server @@ -37,11 +37,10 @@ func TestResolveHTTPS(t *testing.T) { msg := new(dns.Msg) msg.SetQuestion(testCase.Name, testCase.Type) - msg = msg.SetQuestion(testCase.Name, testCase.Type) + // msg = msg.SetQuestion(testCase.Name, testCase.Type) res, err := resolver.LookUp(msg) - assert.Nil(t, err) - assert.NotNil(t, res) - + assert.NilError(t, err) + assert.Assert(t, res != helpers.Response{}) } func Test2ResolveHTTPS(t *testing.T) { @@ -51,17 +50,17 @@ func Test2ResolveHTTPS(t *testing.T) { Logger: util.InitLogger(0), } var err error - testCase := structs.Request{Server: "dns9.quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone"} + testCase := helpers.Request{Server: "dns9.quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone"} resolver, err := query.LoadResolver(testCase.Server, opts) - assert.Nil(t, err) + assert.NilError(t, err) msg := new(dns.Msg) msg.SetQuestion(testCase.Name, testCase.Type) - msg = msg.SetQuestion(testCase.Name, testCase.Type) + // msg = msg.SetQuestion(testCase.Name, testCase.Type) res, err := resolver.LookUp(msg) - assert.Error(t, err) - assert.Equal(t, res, structs.Response{}) - + assert.ErrorContains(t, err, "fully qualified") + assert.Equal(t, res, helpers.Response{}) } + func Test3ResolveHTTPS(t *testing.T) { t.Parallel() opts := cli.Options{ @@ -69,7 +68,7 @@ func Test3ResolveHTTPS(t *testing.T) { Logger: util.InitLogger(0), } var err error - testCase := structs.Request{Server: "dns9..quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone."} + testCase := helpers.Request{Server: "dns9..quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone."} if !strings.HasPrefix(testCase.Server, "https://") { testCase.Server = "https://" + testCase.Server } @@ -78,12 +77,11 @@ func Test3ResolveHTTPS(t *testing.T) { testCase.Name = fmt.Sprintf("%s.", testCase.Name) } resolver, err := query.LoadResolver(testCase.Server, opts) - assert.Nil(t, err) + assert.NilError(t, err) msg := new(dns.Msg) msg.SetQuestion(testCase.Name, testCase.Type) - msg = msg.SetQuestion(testCase.Name, testCase.Type) + // msg = msg.SetQuestion(testCase.Name, testCase.Type) res, err := resolver.LookUp(msg) - assert.Error(t, err) - assert.Equal(t, res, structs.Response{}) - + assert.ErrorContains(t, err, "request error") + assert.Equal(t, res, helpers.Response{}) } diff --git a/query/QUIC.go b/query/QUIC.go index 3026cee..e9aa373 100644 --- a/query/QUIC.go +++ b/query/QUIC.go @@ -8,7 +8,7 @@ import ( "time" "git.froth.zone/sam/awl/cli" - "git.froth.zone/sam/awl/internal/structs" + "git.froth.zone/sam/awl/internal/helpers" "github.com/lucas-clemente/quic-go" "github.com/miekg/dns" @@ -19,8 +19,8 @@ type QUICResolver struct { opts cli.Options } -func (r *QUICResolver) LookUp(msg *dns.Msg) (structs.Response, error) { - var resp structs.Response +func (r *QUICResolver) LookUp(msg *dns.Msg) (helpers.Response, error) { + var resp helpers.Response tls := &tls.Config{ MinVersion: tls.VersionTLS12, NextProtos: []string{"doq"}, @@ -28,46 +28,46 @@ func (r *QUICResolver) LookUp(msg *dns.Msg) (structs.Response, error) { r.opts.Logger.Debug("making DoQ request") connection, err := quic.DialAddr(r.server, tls, nil) if err != nil { - return structs.Response{}, err + return helpers.Response{}, err } // Compress request to over-the-wire buf, err := msg.Pack() if err != nil { - return structs.Response{}, err + return helpers.Response{}, err } t := time.Now() stream, err := connection.OpenStream() if err != nil { - return structs.Response{}, err + return helpers.Response{}, err } _, err = stream.Write(buf) if err != nil { - return structs.Response{}, err + return helpers.Response{}, err } fullRes, err := io.ReadAll(stream) if err != nil { - return structs.Response{}, err + return helpers.Response{}, err } resp.RTT = time.Since(t) // Close with error: no error err = connection.CloseWithError(0, "") if err != nil { - return structs.Response{}, err + return helpers.Response{}, err } err = stream.Close() if err != nil { - return structs.Response{}, err + return helpers.Response{}, err } resp.DNS = &dns.Msg{} r.opts.Logger.Debug("unpacking DoQ response") err = resp.DNS.Unpack(fullRes) if err != nil { - return structs.Response{}, err + return helpers.Response{}, err } return resp, nil } diff --git a/query/QUIC_test.go b/query/QUIC_test.go index bc765f7..751d0b2 100644 --- a/query/QUIC_test.go +++ b/query/QUIC_test.go @@ -10,12 +10,12 @@ import ( "testing" "git.froth.zone/sam/awl/cli" - "git.froth.zone/sam/awl/internal/structs" + "git.froth.zone/sam/awl/internal/helpers" "git.froth.zone/sam/awl/query" "git.froth.zone/sam/awl/util" "github.com/miekg/dns" - "github.com/stretchr/testify/assert" + "gotest.tools/v3/assert" ) func TestQuic(t *testing.T) { @@ -24,18 +24,18 @@ func TestQuic(t *testing.T) { QUIC: true, Logger: util.InitLogger(0), Port: 853, - Request: structs.Request{Server: "dns.adguard.com"}, + Request: helpers.Request{Server: "dns.adguard.com"}, } - testCase := structs.Request{Server: "dns.//./,,adguard.com", Type: dns.TypeA, Name: "git.froth.zone"} - testCase2 := structs.Request{Server: "dns.adguard.com", Type: dns.TypeA, Name: "git.froth.zone"} - var testCases []structs.Request + testCase := helpers.Request{Server: "dns.//./,,adguard.com", Type: dns.TypeA, Name: "git.froth.zone"} + testCase2 := helpers.Request{Server: "dns.adguard.com", Type: dns.TypeA, Name: "git.froth.zone"} + var testCases []helpers.Request testCases = append(testCases, testCase) testCases = append(testCases, testCase2) for i := range testCases { switch i { case 0: resolver, err := query.LoadResolver(testCases[i].Server, opts) - assert.Nil(t, err) + assert.NilError(t, err) // if the domain is not canonical, make it canonical if !strings.HasSuffix(testCase.Name, ".") { testCases[i].Name = fmt.Sprintf("%s.", testCases[i].Name) @@ -44,11 +44,11 @@ func TestQuic(t *testing.T) { msg.SetQuestion(testCase.Name, testCase.Type) msg = msg.SetQuestion(testCase.Name, testCase.Type) res, err := resolver.LookUp(msg) - assert.NotNil(t, err) - assert.Equal(t, res, structs.Response{}) + assert.ErrorContains(t, err, "fully qualified") + assert.Equal(t, res, helpers.Response{}) case 1: resolver, err := query.LoadResolver(testCase2.Server, opts) - assert.Nil(t, err) + assert.NilError(t, err) testCase2.Server = net.JoinHostPort(testCase2.Server, strconv.Itoa(opts.Port)) // if the domain is not canonical, make it canonical if !strings.HasSuffix(testCase2.Name, ".") { @@ -58,10 +58,8 @@ func TestQuic(t *testing.T) { msg.SetQuestion(testCase2.Name, testCase2.Type) msg = msg.SetQuestion(testCase2.Name, testCase2.Type) res, err := resolver.LookUp(msg) - assert.Nil(t, err) - assert.NotNil(t, res) + assert.NilError(t, err) + assert.Assert(t, res != helpers.Response{}) } - } - } diff --git a/query/general.go b/query/general.go index b92783a..2a29fab 100644 --- a/query/general.go +++ b/query/general.go @@ -6,7 +6,7 @@ import ( "fmt" "git.froth.zone/sam/awl/cli" - "git.froth.zone/sam/awl/internal/structs" + "git.froth.zone/sam/awl/internal/helpers" "github.com/miekg/dns" ) @@ -16,9 +16,9 @@ type StandardResolver struct { opts cli.Options } -func (r *StandardResolver) LookUp(msg *dns.Msg) (structs.Response, error) { +func (r *StandardResolver) LookUp(msg *dns.Msg) (helpers.Response, error) { var ( - resp structs.Response + resp helpers.Response err error ) dnsClient := new(dns.Client) @@ -41,7 +41,7 @@ func (r *StandardResolver) LookUp(msg *dns.Msg) (structs.Response, error) { resp.DNS, resp.RTT, err = dnsClient.Exchange(msg, r.server) if err != nil { - return structs.Response{}, err + return helpers.Response{}, err } if resp.DNS.MsgHdr.Truncated && !r.opts.Truncate { diff --git a/query/general_test.go b/query/general_test.go new file mode 100644 index 0000000..2b8d82f --- /dev/null +++ b/query/general_test.go @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package query_test + +import ( + "testing" + + "git.froth.zone/sam/awl/cli" + "git.froth.zone/sam/awl/internal/helpers" + "git.froth.zone/sam/awl/query" + "git.froth.zone/sam/awl/util" + "github.com/miekg/dns" + "gotest.tools/v3/assert" +) + +func TestResolve(t *testing.T) { + t.Parallel() + opts := cli.Options{ + Logger: util.InitLogger(0), + Port: 53, + Request: helpers.Request{ + Server: "8.8.4.4", + Type: dns.TypeA, + Name: "example.com.", + }, + } + resolver, err := query.LoadResolver(opts.Request.Server, opts) + assert.NilError(t, err) + msg := new(dns.Msg) + msg.SetQuestion(opts.Request.Name, opts.Request.Type) + res, err := resolver.LookUp(msg) + assert.NilError(t, err) + assert.Assert(t, res != helpers.Response{}) +} + +func TestTruncate(t *testing.T) { + t.Parallel() + opts := cli.Options{ + Logger: util.InitLogger(0), + Port: 5301, + Request: helpers.Request{ + Server: "madns.binarystar.systems", + Type: dns.TypeTXT, + Name: "limit.txt.example.", + }, + } + resolver, err := query.LoadResolver(opts.Request.Server, opts) + assert.NilError(t, err) + msg := new(dns.Msg) + msg.SetQuestion(opts.Request.Name, opts.Request.Type) + res, err := resolver.LookUp(msg) + assert.NilError(t, err) + assert.Assert(t, res != helpers.Response{}) +} diff --git a/query/query.go b/query/query.go index 4421bd9..f069113 100644 --- a/query/query.go +++ b/query/query.go @@ -1,16 +1,19 @@ +// SPDX-License-Identifier: BSD-3-Clause + package query import ( "git.froth.zone/sam/awl/cli" - "git.froth.zone/sam/awl/internal/structs" + "git.froth.zone/sam/awl/internal/helpers" "github.com/miekg/dns" ) -func CreateQuery(opts cli.Options) (structs.Response, error) { - var res structs.Response +func CreateQuery(opts cli.Options) (helpers.Response, error) { + var res helpers.Response res.DNS = new(dns.Msg) - res.DNS.SetQuestion(opts.Query, opts.Type) + res.DNS.SetQuestion(opts.Request.Name, opts.Request.Type) + res.DNS.Question[0].Qclass = opts.Request.Class res.DNS.MsgHdr.Response = opts.QR res.DNS.MsgHdr.Authoritative = opts.AA @@ -27,7 +30,7 @@ func CreateQuery(opts cli.Options) (structs.Response, error) { resolver, err := LoadResolver(opts.Request.Server, opts) if err != nil { - return structs.Response{}, err + return helpers.Response{}, err } return resolver.LookUp(res.DNS) diff --git a/query/query_test.go b/query/query_test.go new file mode 100644 index 0000000..ab00cfa --- /dev/null +++ b/query/query_test.go @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package query_test + +import ( + "testing" + + "git.froth.zone/sam/awl/cli" + "git.froth.zone/sam/awl/internal/helpers" + "git.froth.zone/sam/awl/query" + "git.froth.zone/sam/awl/util" + + "github.com/miekg/dns" + "gotest.tools/v3/assert" +) + +func TestCreateQ(t *testing.T) { + opts := cli.Options{ + Logger: util.InitLogger(0), + Port: 53, + QR: false, + Z: true, + RD: false, + DNSSEC: true, + Request: helpers.Request{ + Server: "8.8.4.4", + Type: dns.TypeA, + Name: "example.com.", + }, + } + res, err := query.CreateQuery(opts) + assert.NilError(t, err) + assert.Assert(t, res != helpers.Response{}) +} diff --git a/query/resolver.go b/query/resolver.go index f6df45e..e5484fd 100644 --- a/query/resolver.go +++ b/query/resolver.go @@ -8,12 +8,12 @@ import ( "strings" "git.froth.zone/sam/awl/cli" - "git.froth.zone/sam/awl/internal/structs" + "git.froth.zone/sam/awl/internal/helpers" "github.com/miekg/dns" ) type Resolver interface { - LookUp(*dns.Msg) (structs.Response, error) + LookUp(*dns.Msg) (helpers.Response, error) } func LoadResolver(server string, opts cli.Options) (Resolver, error) { diff --git a/util/logger.go b/util/logger.go index 4a3b2a7..037d4d7 100644 --- a/util/logger.go +++ b/util/logger.go @@ -4,7 +4,7 @@ package util import "git.froth.zone/sam/awl/logawl" -// Initialize the logawl instance +// Initialize the logawl instance. func InitLogger(verbosity int) (Logger *logawl.Logger) { Logger = logawl.New() diff --git a/util/logger_test.go b/util/logger_test.go new file mode 100644 index 0000000..a9502be --- /dev/null +++ b/util/logger_test.go @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package util_test + +import ( + "testing" + + "git.froth.zone/sam/awl/logawl" + "git.froth.zone/sam/awl/util" + "gotest.tools/v3/assert" +) + +func TestInitLogger(t *testing.T) { + logger := util.InitLogger(0) + assert.Equal(t, logger.Level, logawl.Level(0)) +} diff --git a/util/reverseDNS.go b/util/reverseDNS.go index bd153e9..57d800e 100644 --- a/util/reverseDNS.go +++ b/util/reverseDNS.go @@ -10,7 +10,7 @@ import ( "github.com/miekg/dns" ) -// Given an IP or phone number, return a canonical string to be queried +// Given an IP or phone number, return a canonical string to be queried. func ReverseDNS(address string, querInt uint16) (string, error) { query := dns.TypeToString[querInt] if query == "PTR" { @@ -33,11 +33,10 @@ func ReverseDNS(address string, querInt uint16) (string, error) { return "", errors.New("ReverseDNS: -x flag given but no IP found") } -// Reverse a string, return the string in reverse +// Reverse a string, return the string in reverse. func reverse(s string) string { rns := []rune(s) for i, j := 0, len(rns)-1; i < j; i, j = i+1, j-1 { - rns[i], rns[j] = rns[j], rns[i] } return string(rns) diff --git a/util/reverseDNS_test.go b/util/reverseDNS_test.go index fe1d16f..7841e40 100644 --- a/util/reverseDNS_test.go +++ b/util/reverseDNS_test.go @@ -1,12 +1,14 @@ // SPDX-License-Identifier: BSD-3-Clause -package util +package util_test import ( "testing" + "git.froth.zone/sam/awl/util" + "github.com/miekg/dns" - "github.com/stretchr/testify/assert" + "gotest.tools/v3/assert" ) var ( @@ -16,15 +18,15 @@ var ( func TestIPv4(t *testing.T) { t.Parallel() - act, err := ReverseDNS("8.8.4.4", PTR) - assert.Nil(t, err) + act, err := util.ReverseDNS("8.8.4.4", PTR) + assert.NilError(t, err) assert.Equal(t, act, "4.4.8.8.in-addr.arpa.", "IPv4 reverse") } func TestIPv6(t *testing.T) { t.Parallel() - act, err := ReverseDNS("2606:4700:4700::1111", PTR) - assert.Nil(t, err) + act, err := util.ReverseDNS("2606:4700:4700::1111", PTR) + assert.NilError(t, err) assert.Equal(t, act, "1.1.1.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.7.4.0.0.7.4.6.0.6.2.ip6.arpa.", "IPv6 reverse") } @@ -40,14 +42,19 @@ func TestNAPTR(t *testing.T) { {"17705551212", "2.1.2.1.5.5.5.0.7.7.1.e164.arpa."}, } for _, test := range tests { - act, err := ReverseDNS(test.in, NAPTR) - assert.Nil(t, err) - assert.Equal(t, test.want, act) + // Thanks Goroutines, very cool! + test := test + t.Run(test.in, func(t *testing.T) { + t.Parallel() + act, err := util.ReverseDNS(test.in, NAPTR) + assert.NilError(t, err) + assert.Equal(t, test.want, act) + }) } } func TestInvalid(t *testing.T) { t.Parallel() - _, err := ReverseDNS("AAAAA", 1) - assert.Error(t, err) + _, err := util.ReverseDNS("AAAAA", 1) + assert.ErrorContains(t, err, "no IP found") }