diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 1599481..0000000 --- a/.drone.yml +++ /dev/null @@ -1,124 +0,0 @@ ---- -{ - "kind": "pipeline", - "name": "1.18-amd64", - "platform": { - "arch": "amd64" - }, - "steps": [ - { - "commands": [ - "git submodule update --init --recursive" - ], - "image": "alpine/git", - "name": "submodules" - }, - { - "depends_on": [ - "submodules" - ], - "image": "rancher/drone-golangci-lint:latest", - "name": "lint" - }, - { - "commands": [ - "go test -v -race ./... -cover" - ], - "depends_on": [ - "submodules" - ], - "image": "golang:1.18", - "name": "test" - } - ], - "trigger": { - "event": { - "exclude": [ - "tag" - ] - } - }, - "type": "docker" -} ---- -{ - "kind": "pipeline", - "name": "1.18-arm64", - "platform": { - "arch": "arm64" - }, - "steps": [ - { - "commands": [ - "git submodule update --init --recursive" - ], - "image": "alpine/git", - "name": "submodules" - }, - { - "depends_on": [ - "submodules" - ], - "image": "rancher/drone-golangci-lint:latest", - "name": "lint" - }, - { - "commands": [ - "go test -v -race ./... -cover" - ], - "depends_on": [ - "submodules" - ], - "image": "golang:1.18", - "name": "test" - } - ], - "trigger": { - "event": { - "exclude": [ - "tag" - ] - } - }, - "type": "docker" -} ---- -{ - "kind": "pipeline", - "name": "release", - "steps": [ - { - "commands": [ - "git fetch --tags", - "git submodule update --init --recursive" - ], - "image": "alpine/git", - "name": "fetch" - }, - { - "commands": [ - "go test -race ./... -cover" - ], - "image": "golang", - "name": "test" - }, - { - "commands": [ - "goreleaser release" - ], - "environment": { - "GITEA_TOKEN": { - "from_secret": "GITEA_TOKEN" - } - }, - "image": "goreleaser/goreleaser", - "name": "release" - } - ], - "trigger": { - "event": [ - "tag" - ] - }, - "type": "docker" -} diff --git a/cli/cli.go b/cli/cli.go index d034374..4b09779 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -4,7 +4,6 @@ package cli import ( "fmt" - "net" "os" "runtime" "strconv" @@ -13,12 +12,12 @@ import ( "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. +// ParseCLI parses arguments given from the CLI and passes them into an `Options` +// struct. func ParseCLI(version string) (Options, error) { flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) @@ -89,7 +88,7 @@ func ParseCLI(version string) (Options, error) { noOpt = flag.Bool("no-opt", false, "disable printing the OPT pseudosection") noAns = flag.Bool("no-answer", false, "disable printing the answer section") noAuth = flag.Bool("no-authority", false, "disable printing the authority section") - noAdd = flag.Bool("no-additional", false, "disable printing the additonal section") + noAdd = flag.Bool("no-additional", false, "disable printing the additional section") noStats = flag.Bool("no-statistics", false, "disable printing the statistics section") verbosity = flag.Int("verbosity", 1, "sets verbosity `level`", flag.OptShorthand('v'), flag.OptNoOptDefVal("2")) @@ -102,13 +101,13 @@ func ParseCLI(version string) (Options, error) { // Parse the flags err := flag.CommandLine.Parse(os.Args[1:]) if err != nil { - return Options{Logger: util.InitLogger(*verbosity)}, err + return Options{Logger: util.InitLogger(*verbosity)}, fmt.Errorf("flag parse error: %w", err) } // TODO: DRY, dumb dumb. mbz, err := strconv.ParseInt(*zflag, 0, 16) if err != nil { - return Options{Logger: util.InitLogger(*verbosity)}, err + return Options{Logger: util.InitLogger(*verbosity)}, fmt.Errorf("EDNS MBZ error: %w", err) } opts := Options{ @@ -170,33 +169,9 @@ func ParseCLI(version string) (Options, error) { // TODO: DRY if *subnet != "" { - ip, inet, err := net.ParseCIDR(*subnet) + err := parseSubnet(*subnet, &opts) if err != nil { - // TODO: make not a default? - if *subnet == "0" { - opts.EDNS.Subnet = dns.EDNS0_SUBNET{ - Code: dns.EDNS0SUBNET, - Family: 1, - SourceNetmask: 0, - SourceScope: 0, - Address: net.IPv4(0, 0, 0, 0), - } - } else { - return Options{Logger: util.InitLogger(*verbosity)}, err - } - } else { - sub, _ := inet.Mask.Size() - opts.EDNS.Subnet = *new(dns.EDNS0_SUBNET) - opts.EDNS.Subnet.Address = ip - opts.EDNS.Subnet.SourceNetmask = uint8(sub) - switch ip.To4() { - case nil: - // Not a valid IPv4 so assume IPv6 - opts.EDNS.Subnet.Family = 2 - default: - // Valid IPv4 - opts.EDNS.Subnet.Family = 1 - } + return opts, err } } diff --git a/cli/cli_test.go b/cli/cli_test.go index 93a5a4f..edeb947 100644 --- a/cli/cli_test.go +++ b/cli/cli_test.go @@ -11,6 +11,7 @@ import ( "gotest.tools/v3/assert" ) +// nolint: paralleltest func TestEmpty(t *testing.T) { old := os.Args os.Args = []string{"awl", "-4"} @@ -21,6 +22,7 @@ func TestEmpty(t *testing.T) { os.Args = old } +// nolint: paralleltest func TestTLSPort(t *testing.T) { old := os.Args os.Args = []string{"awl", "-T"} @@ -30,6 +32,7 @@ func TestTLSPort(t *testing.T) { os.Args = old } +// nolint: paralleltest func TestSubnet(t *testing.T) { old := os.Args os.Args = []string{"awl", "--subnet", "127.0.0.1/32"} @@ -50,6 +53,7 @@ func TestSubnet(t *testing.T) { os.Args = old } +// nolint: paralleltest func TestInvalidFlag(t *testing.T) { old := os.Args os.Args = []string{"awl", "--treebug"} @@ -58,14 +62,16 @@ func TestInvalidFlag(t *testing.T) { os.Args = old } +// nolint: paralleltest func TestInvalidDig(t *testing.T) { old := os.Args os.Args = []string{"awl", "+a"} _, err := cli.ParseCLI("TEST") - assert.ErrorContains(t, err, "digflags: unknown flag") + assert.ErrorContains(t, err, "digflags: invalid argument") os.Args = old } +// nolint: paralleltest func TestVersion(t *testing.T) { old := os.Args os.Args = []string{"awl", "--version"} @@ -74,6 +80,7 @@ func TestVersion(t *testing.T) { os.Args = old } +// nolint: paralleltest func TestTimeout(t *testing.T) { args := [][]string{ {"awl", "+timeout=0"}, @@ -89,6 +96,7 @@ func TestTimeout(t *testing.T) { } } +// nolint: paralleltest func TestRetries(t *testing.T) { args := [][]string{ {"awl", "+retry=-2"}, @@ -105,6 +113,7 @@ func TestRetries(t *testing.T) { } } +// nolint: paralleltest func FuzzFlags(f *testing.F) { testcases := []string{"git.froth.zone", "", "!12345", "google.com.edu.org.fr"} for _, tc := range testcases { diff --git a/cli/dig.go b/cli/dig.go index 8a09220..3f48022 100644 --- a/cli/dig.go +++ b/cli/dig.go @@ -4,15 +4,15 @@ package cli import ( "fmt" - "net" "strconv" "strings" "time" - - "github.com/miekg/dns" ) -// Parse dig-like commands and set the options as such. +// ParseDig parses commands from the popular DNS tool dig. +// All dig commands are taken from https://man.openbsd.org/dig.1 as the source of their functionality. +// +// [no]flags are supported just as flag are and are disabled as such. func ParseDig(arg string, opts *Options) error { // returns true if the flag starts with a no isNo := !strings.HasPrefix(arg, "no") @@ -121,11 +121,9 @@ func ParseDig(arg string, opts *Options) error { // Recursive switch statements WOO arg := strings.Split(arg, "=") switch arg[0] { - case "time", "timeout": if len(arg) > 1 && arg[1] != "" { timeout, err := strconv.Atoi(arg[1]) - if err != nil { return fmt.Errorf("digflags: Invalid timeout value: %w", err) } @@ -188,40 +186,16 @@ func ParseDig(arg string, opts *Options) error { case "subnet": if len(arg) > 1 && arg[1] != "" { - ip, inet, err := net.ParseCIDR(arg[1]) + err := parseSubnet(arg[1], opts) if err != nil { - // TODO: make not a default? - if arg[1] == "0" { - opts.EDNS.Subnet = dns.EDNS0_SUBNET{ - Code: dns.EDNS0SUBNET, - Family: 1, - SourceNetmask: 0, - SourceScope: 0, - Address: net.IPv4(0, 0, 0, 0), - } - return nil - } else { - return fmt.Errorf("digflags: Invalid EDNS Subnet: %w", errNoArg) - } - } - subnet, _ := inet.Mask.Size() - opts.EDNS.Subnet = *new(dns.EDNS0_SUBNET) - opts.EDNS.Subnet.Address = ip - opts.EDNS.Subnet.SourceNetmask = uint8(subnet) - switch ip.To4() { - case nil: - // Not a valid IPv4 so assume IPv6 - opts.EDNS.Subnet.Family = 2 - default: - // Valid IPv4 - opts.EDNS.Subnet.Family = 1 + return fmt.Errorf("digflags: Invalid EDNS Subnet: %w", err) } } else { return fmt.Errorf("digflags: Invalid EDNS Subnet: %w", errNoArg) } default: - return fmt.Errorf("digflags: unknown flag %s given", arg) + return &errInvalidArg{arg[0]} } } return nil diff --git a/cli/misc.go b/cli/misc.go index 6d8e1b7..282d52d 100644 --- a/cli/misc.go +++ b/cli/misc.go @@ -9,12 +9,12 @@ import ( "git.froth.zone/sam/awl/conf" "git.froth.zone/sam/awl/util" - "github.com/miekg/dns" "golang.org/x/net/idna" ) -// Parse the wildcard arguments, drill style. +// ParseMiscArgs parses the wildcard arguments, drill style. +// Only one command is supported at a time, so any extra information overrides previous. func ParseMiscArgs(args []string, opts *Options) error { var err error @@ -60,7 +60,7 @@ func ParseMiscArgs(args []string, opts *Options) error { opts.Logger.Info(arg, "detected as a domain name") opts.Request.Name, err = idna.ToASCII(arg) if err != nil { - return err + return fmt.Errorf("punycode translate error: %w", err) } // DNS query type @@ -73,7 +73,7 @@ func ParseMiscArgs(args []string, opts *Options) error { opts.Logger.Info(arg, "is unknown. Assuming domain") opts.Request.Name, err = idna.ToASCII(arg) if err != nil { - return err + return fmt.Errorf("punycode translate error: %w", err) } } } @@ -113,21 +113,23 @@ func ParseMiscArgs(args []string, opts *Options) error { opts.Request.Server = "95.216.99.249" } else { // Make sure that if IPv4 or IPv6 is asked for it actually uses it + harmful: for _, srv := range resolv.Servers { - if opts.IPv4 { + switch { + case opts.IPv4: if strings.Contains(srv, ".") { opts.Request.Server = srv - break + break harmful } - } else if opts.IPv6 { + case opts.IPv6: if strings.Contains(srv, ":") { opts.Request.Server = srv - break + break harmful } - } else { + default: //#nosec -- This isn't used for anything secure opts.Request.Server = resolv.Servers[rand.Intn(len(resolv.Servers))] - break + break harmful } } } @@ -143,7 +145,7 @@ func ParseMiscArgs(args []string, opts *Options) error { } opts.Request.Name, err = util.ReverseDNS(opts.Request.Name, opts.Request.Type) if err != nil { - return err + return fmt.Errorf("reverse DNS error: %w", err) } } diff --git a/cli/misc_test.go b/cli/misc_test.go index 929c1b4..80afee5 100644 --- a/cli/misc_test.go +++ b/cli/misc_test.go @@ -8,7 +8,6 @@ import ( "git.froth.zone/sam/awl/cli" "git.froth.zone/sam/awl/util" - "github.com/miekg/dns" "gotest.tools/v3/assert" ) @@ -139,7 +138,6 @@ func TestFlagSetting(t *testing.T) { assert.Assert(t, opts.HTTPS) case 3: assert.Assert(t, opts.QUIC) - } }) } diff --git a/cli/options.go b/cli/options.go index 9183e38..9508093 100644 --- a/cli/options.go +++ b/cli/options.go @@ -4,10 +4,11 @@ package cli import ( "errors" + "fmt" + "net" "git.froth.zone/sam/awl/internal/helpers" "git.froth.zone/sam/awl/logawl" - "github.com/miekg/dns" ) @@ -35,7 +36,7 @@ type Options struct { Reverse bool // Make reverse query Verbosity int // Set logawl verbosity HumanTTL bool // Make TTL human readable - ShowTTL bool //Display TTL + ShowTTL bool // Display TTL Short bool // Short output Identify bool // If short, add identity stuffs JSON bool // Outout as JSON @@ -47,7 +48,7 @@ type Options struct { EDNS // EDNS } -// What to (and not to) display +// What to (and not to) display. type Displays struct { Comments bool Question bool // QUESTION SECTION @@ -73,6 +74,48 @@ type EDNS struct { Subnet dns.EDNS0_SUBNET // EDNS Subnet (duh) } +// parseSubnet takes a subnet argument and makes it into one that the DNS library +// understands. +func parseSubnet(subnet string, opts *Options) error { + ip, inet, err := net.ParseCIDR(subnet) + if err != nil { + // TODO: make not a default? + if subnet == "0" { + opts.EDNS.Subnet = dns.EDNS0_SUBNET{ + Code: dns.EDNS0SUBNET, + Family: 1, + SourceNetmask: 0, + SourceScope: 0, + Address: net.IPv4(0, 0, 0, 0), + } + return nil + } + return fmt.Errorf("subnet parsing error %w", err) + } + sub, _ := inet.Mask.Size() + opts.EDNS.Subnet = dns.EDNS0_SUBNET{} + opts.EDNS.Subnet.Address = ip + opts.EDNS.Subnet.SourceNetmask = uint8(sub) + + switch ip.To4() { + case nil: + // Not a valid IPv4 so assume IPv6 + opts.EDNS.Subnet.Family = 2 + default: + // Valid IPv4 + opts.EDNS.Subnet.Family = 1 + } + return nil +} + var ErrNotError = errors.New("not an error") var errNoArg = errors.New("no argument given") + +type errInvalidArg struct { + arg string +} + +func (e *errInvalidArg) Error() string { + return fmt.Sprintf("digflags: invalid argument %s", e.arg) +} diff --git a/conf/plan9.go b/conf/plan9.go index 2819c92..9da5690 100644 --- a/conf/plan9.go +++ b/conf/plan9.go @@ -3,7 +3,7 @@ package conf import ( - "fmt" + "errors" "strings" "github.com/miekg/dns" @@ -23,7 +23,7 @@ func GetPlan9Config(str string) (*dns.ClientConfig, error) { } } if len(servers) == 0 { - return nil, fmt.Errorf("plan9: no DNS servers found") + return nil, errPlan9 } // TODO: read more about how customizable Plan 9 is @@ -38,3 +38,5 @@ func GetPlan9Config(str string) (*dns.ClientConfig, error) { func splitChars(r rune) bool { return r == ' ' || r == '\t' } + +var errPlan9 = errors.New("plan9Config: no DNS servers found") diff --git a/conf/plan9_test.go b/conf/plan9_test.go index af8379a..a5123fc 100644 --- a/conf/plan9_test.go +++ b/conf/plan9_test.go @@ -6,7 +6,6 @@ import ( "testing" "git.froth.zone/sam/awl/conf" - "gotest.tools/v3/assert" ) diff --git a/conf/unix.go b/conf/unix.go index 12eb49f..9809b48 100644 --- a/conf/unix.go +++ b/conf/unix.go @@ -4,6 +4,7 @@ package conf import ( + "fmt" "os" "runtime" @@ -15,7 +16,7 @@ func GetDNSConfig() (*dns.ClientConfig, error) { if runtime.GOOS == "plan9" { dat, err := os.ReadFile("/net/ndb") if err != nil { - return nil, err + return nil, fmt.Errorf("plan9 ndb: %w", err) } return GetPlan9Config(string(dat)) } else { diff --git a/conf/win.go b/conf/win.go index 2e5d23c..6b5712e 100644 --- a/conf/win.go +++ b/conf/win.go @@ -4,6 +4,7 @@ package conf import ( + "fmt" "strings" "unsafe" @@ -23,7 +24,7 @@ func GetDNSConfig() (*dns.ClientConfig, error) { // Windows is an utter fucking trash fire of an operating system. if err := windows.GetAdaptersAddresses(windows.AF_UNSPEC, windows.GAA_FLAG_INCLUDE_PREFIX, 0, (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])), &l); err != nil { - return nil, err + return nil, fmt.Errorf("config, windows: %w", err) } var addresses []*windows.IpAdapterAddresses for addr := (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])); addr != nil; addr = addr.Next { diff --git a/doc/awl.1 b/doc/awl.1 index 27e4a92..1da461e 100644 --- a/doc/awl.1 +++ b/doc/awl.1 @@ -5,7 +5,7 @@ .nh .ad l .\" Begin generated content: -.TH "awl" "1" "2022-08-01" +.TH "awl" "1" "2022-08-03" .PP .SH NAME awl - DNS lookup tool @@ -23,11 +23,11 @@ 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, +\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 leatherworking.\& .PP -\fBawl\fR is designed to be a more "modern" version of \fIdrill\fR(1) by including +\fBawl\fR is designed to be a more "modern" version of \fIdrill\fR(1) by including some more recent RFCs and output options.\& \fBawl\fR is still heavily Work-In-Progress so some features may get added or removed.\& .PP @@ -39,10 +39,6 @@ Dig-like +[no]flags are supported, see dig(1) .br Enable DNSSEC.\& This needs to be manually enabled.\& .PP -\fB--qr\fR, \fB+qr\fR -.br - Print query before sending.\& -.PP \fB-v\fR \fIvalue\fR .br Set verbosity (currently WIP) @@ -132,7 +128,7 @@ Dig-like +[no]flags are supported, see dig(1) .br 0.\&5 seconds is the minimum.\& .PP -\fB--retries\fR \fIint\fR, \fB+tries\fR=\fIint\fR, \fB+retry\fR=\fIint\fR +\fB--retries\fR \fIint\fR, \fB+tries\fR=\fIint\fR, \fB+ retry\fR=\fIint\fR .br Set the number of retries.\& .br @@ -142,109 +138,61 @@ Dig-like +[no]flags are supported, see dig(1) .SS DNS Flags .PP .RS 4 -\fB--aa\fR[\fI=false\fR], \fB+[no]aaflag\fR +\fB--aa=[false]\fR, \fB+[no]aaflag\fR .br (Set, Unset) AA (Authoritative Answer) flag .PP -\fB--ad\fR[\fI=false\fR], \fB+[no]adflag\fR +\fB--ad=[false]\fR, \fB+[no]adflag\fR .br (Set, Unset) AD (Authenticated Data) flag .PP -\fB--tc\fR[\fI=false\fR], \fB+[no]tcflag\fR +\fB--tc=[false]\fR, \fB+[no]tcflag\fR .br (Set, Unset) TC (TrunCated) flag .PP -\fB-z\fR[\fI=false\fR], \fB+[no]zflag\fR +\fB-z=[false]\fR, \fB+[no]zflag\fR .br (Set, Unset) Z (Zero) flag .PP -\fB--cd\fR[\fI=false\fR], \fB+[no]cdflag\fR +\fB--cd=[false]\fR, \fB+[no]cdflag\fR .br (Set, Unset) CD (Checking Disabled) flag .PP -\fB--qr\fR[\fI=false\fR], \fB+[no]qrflag\fR +\fB--qr=[false]\fR, \fB+[no]qrflag\fR .br (Set, Unset) QR (QueRy) flag .PP -\fB--rd\fR[\fI=true\fR], \fB+[no]rdflag\fR +\fB--rd=[true]\fR, \fB+[no]rdflag\fR .br (Set, Unset) RD (Recursion Desired) flag .PP -\fB--ra\fR[\fI=false\fR], \fB+[no]raflag\fR +\fB--ra=[false]\fR, \fB+[no]raflag\fR .br (Set, Unset) RA (Recursion Available) flag .PP .RE -.SS EDNS Flags -.RS 4 -\fB--edns-ver\fR \fIver\fR, \fB+edns\fR[\fI=ver\fR] -.br - Set EDNS version to \fIver\fR (default 0) -.PP -\fB--no-edns\fR, \fB+noedns\fR -.br - \fB--no-edns\fR and \fB+noedns\fR disable EDNS entirely -.PP -\fB--expire\fR, \fB+[no]expire\fR -.br - Add EDNS expire option to query -.PP -\fB--nsid\fR, \fB+[no]nsid\fR -.br - Add EDNS NSID option to query -.PP -\fB--no-cookie\fR, \fB+[no]cookie\fR -.br - Send an EDNS cookie (sent by default) -.PP -\fB--keep-alive\fR, \fB+[no]keepalive\fR, \fB+[no]keepopen\fR -.br - Send an EDNS TCP keepalive options -.PP -\fB--buffer-size\fR, \fB+bufsize\fR=\fIint\fR -.br - Set UDP buffer size to int (default 1232) -.PP -\fB--zflags\fR \fIflag\fR, \fB+ednsflags\fR[\fI=flag\fR] -.br - Set Z EDNS flag bits.\& Octal, decimal and hex are accepted.\& -Setting the first bit (DO) will be ignored, use \fB-D\fR instead -.PP -\fB--pad\fR, \fB+padding\fR -.br - Set EDNS padding -.PP -\fB+subnet\fR=\fIaddr/prefix\fR -.br - Set EDNS client subnet -.PP -.RE .SS Output Display .RS 4 -\fB--no-question\fR, \fB+[no]question\fR +\fB--no-question\fR, \fB+noquestion\fR .br Do not display the Question section .PP -\fB--no-answer\fR, \fB+[no]answer\fR +\fB--no-answer\fR, \fB+noanswer\fR .br Do not display the Answer section .PP -\fB--no-answer\fR, \fB+[no]answer\fR +\fB--no-answer\fR, \fB+noanswer\fR .br Do not display the Answer section .PP -\fB--no-authority\fR, \fB+[no]authority\fR +\fB--no-authority\fR, \fB+noauthority\fR .br Do not display the Authority section .PP -\fB--no-additional\fR, \fB+[no]additional\fR +\fB--no-additional\fR, \fB+noadditional\fR .br Do not display the Additional section .PP -\fB--no-comments\fR, \fB+[no]comments\fR -.br - Do not display the query comments -.PP \fB--no-statistics\fR, \fB+nostats\fR .br Do not display the Statistics (additional comments) section @@ -293,10 +241,4 @@ 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 .PP .SH SEE ALSO -\fIdrill\fR(1), \fIdig\fR(1), the many DNS RFCs -.PP -.SH BUGS -Numerous, report them either to -.br -https://git.\&froth.\&zone/sam/awl/issues or via email -~sammefishe/awl-dev@lists.\&sr.\&ht \ No newline at end of file +\fIdrill\fR(1), \fIdig\fR(1), the many DNS RFCs \ No newline at end of file diff --git a/docs.go b/docs.go index 3ba701c..f868989 100644 --- a/docs.go +++ b/docs.go @@ -1,9 +1,8 @@ /* -awl is a DNS lookup tool written in Go, -similar to (and heavily inspired by) drill. +awl is a DNS lookup tool written in Go, similar to (and heavily inspired by) drill. It runs and displays similar outputs to drill, without any frills. -Options are given to print with JSON +Options are given to print with JSON, XML and YAML. Supports results from DNS-over-[UDP, TCP, TLS, HTTPS, QUIC] servers diff --git a/go.mod b/go.mod index d6bfd76..cd6db6b 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/stretchr/testify v1.8.0 // indirect golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/sys v0.0.0-20220803195053-6e608f9ce704 + golang.org/x/sys v0.0.0-20220731174439-a90be440212d golang.org/x/text v0.3.7 // indirect golang.org/x/tools v0.1.12 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect diff --git a/go.sum b/go.sum index 5413b84..ef6e1d5 100644 --- a/go.sum +++ b/go.sum @@ -219,8 +219,6 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220728211354-c7608f3a8462 h1:UreQrH7DbFXSi9ZFox6FNT3WBooWmdANpU+IfkT1T4I= -golang.org/x/net v0.0.0-20220728211354-c7608f3a8462/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b h1:3ogNYyK4oIQdIKzTu68hQrr4iuVxF3AxKl9Aj/eDrw0= golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -263,8 +261,6 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220731174439-a90be440212d h1:Sv5ogFZatcgIMMtBSTTAgMYsicp25MXBubjXNDKwm80= golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220803195053-6e608f9ce704 h1:Y7NOhdqIOU8kYI7BxsgL38d0ot0raxvcW+EMQU2QrT4= -golang.org/x/sys v0.0.0-20220803195053-6e608f9ce704/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/logawl/logawl.go b/logawl/logawl.go index 70c9416..52701b6 100644 --- a/logawl/logawl.go +++ b/logawl/logawl.go @@ -3,7 +3,7 @@ package logawl import ( - "fmt" + "errors" "io" "sync" "sync/atomic" @@ -14,10 +14,10 @@ type ( Logger struct { Mu sync.Mutex Level Level + isDiscard int32 Prefix string Out io.Writer buf []byte - isDiscard int32 } ) @@ -48,7 +48,7 @@ func (l *Logger) UnMarshalLevel(lv Level) (string, error) { case 3: return "DEBUG ", nil } - return "", fmt.Errorf("invalid log level") + return "", errInvalidLevel } func (l *Logger) IsLevel(level Level) bool { @@ -74,3 +74,5 @@ const ( // Verbose log level. DebugLevel ) + +var errInvalidLevel = errors.New("invalid log level") diff --git a/logawl/logger.go b/logawl/logger.go index b703ba2..aaf29a7 100644 --- a/logawl/logger.go +++ b/logawl/logger.go @@ -11,41 +11,41 @@ import ( // Calling New instantiates Logger // -// Level can be changed to one of the other log levels (FatalLevel, ErrorLevel, InfoLevel, DebugLevel) +// Level can be changed to one of the other log levels (ErrorLevel, WarnLevel, InfoLevel, DebugLevel). func New() *Logger { return &Logger{ Out: os.Stderr, - Level: InfoLevel, //Default value is InfoLevel + Level: WarnLevel, // Default value is WarnLevel } } -// Takes any and prints it out to Logger -> Out (io.Writer (default is std.Err)) +// Takes any and prints it out to Logger -> Out (io.Writer (default is std.Err)). func (l *Logger) Println(level Level, v ...any) { if atomic.LoadInt32(&l.isDiscard) != 0 { return } - //If verbose is not set --debug etc print _nothing_ + // If verbose is not set --debug etc print _nothing_ if l.IsLevel(level) { - switch level { //Goes through log levels and does stuff based on them (Fatal os.Exit...etc) + switch level { // Goes through log levels and does stuff based on them (currently nothing) case 0: - err := l.Printer(0, fmt.Sprintln(v...)) //Fatal level + err := l.Printer(0, fmt.Sprintln(v...)) // Error level if err != nil { - fmt.Fprintln(os.Stderr, "FATAL: Logger failed: ", err) + fmt.Fprintln(os.Stderr, "Logger failed: ", err) } case 1: - err := l.Printer(1, fmt.Sprintln(v...)) //Error level + err := l.Printer(1, fmt.Sprintln(v...)) // Warn level if err != nil { - fmt.Fprintln(os.Stderr, "FATAL: Logger failed: ", err) + fmt.Fprintln(os.Stderr, "Logger failed: ", err) } case 2: - err := l.Printer(2, fmt.Sprintln(v...)) //Info level + err := l.Printer(2, fmt.Sprintln(v...)) // Info level if err != nil { - fmt.Fprintln(os.Stderr, "FATAL: Logger failed: ", err) + fmt.Fprintln(os.Stderr, "Logger failed: ", err) } case 3: - err := l.Printer(3, fmt.Sprintln(v...)) //Debug level + err := l.Printer(3, fmt.Sprintln(v...)) // Debug level if err != nil { - fmt.Fprintln(os.Stderr, "FATAL: Logger failed: ", err) + fmt.Fprintln(os.Stderr, "Logger failed: ", err) } default: break @@ -53,7 +53,7 @@ func (l *Logger) Println(level Level, v ...any) { } } -// Formats the log header as such YYYY/MM/DD HH:MM:SS (local time) +// Formats the log header as such YYYY/MM/DD HH:MM:SS (local time) . func (l *Logger) FormatHeader(buf *[]byte, t time.Time, line int, level Level) error { if lvl, err := l.UnMarshalLevel(level); err == nil { // This is ugly but functional @@ -77,12 +77,12 @@ func (l *Logger) FormatHeader(buf *[]byte, t time.Time, line int, level Level) e *buf = append(*buf, ':') *buf = append(*buf, ' ') } else { - return fmt.Errorf("invalid log level choice") + return errInvalidLevel } return nil } -// Printer prints the formatted message directly to stdErr +// Printer prints the formatted message directly to stdErr. func (l *Logger) Printer(level Level, s string) error { now := time.Now() var line int @@ -99,7 +99,10 @@ func (l *Logger) Printer(level Level, s string) error { l.buf = append(l.buf, '\n') } _, err = l.Out.Write(l.buf) - return err + if err != nil { + return fmt.Errorf("logger printing error %w", err) + } + return nil } // Some line formatting stuff from Golang log stdlib file @@ -123,22 +126,22 @@ func formatter(buf *[]byte, i int, wid int) { *buf = append(*buf, b[bp:]...) } -// Call print directly with Debug level +// Call print directly with Debug level. func (l *Logger) Debug(v ...any) { l.Println(DebugLevel, v...) } -// Call print directly with Info level +// Call print directly with Info level. func (l *Logger) Info(v ...any) { l.Println(InfoLevel, v...) } -// Call print directly with Warn level +// Call print directly with Warn level. func (l *Logger) Warn(v ...any) { l.Println(WarnLevel, v...) } -// Call print directly with Error level +// Call print directly with Error level. func (l *Logger) Error(v ...any) { l.Println(ErrLevel, v...) } diff --git a/logawl/logging_test.go b/logawl/logging_test.go index ffbcf60..e30eef9 100644 --- a/logawl/logging_test.go +++ b/logawl/logging_test.go @@ -8,7 +8,6 @@ import ( "time" "git.froth.zone/sam/awl/logawl" - "gotest.tools/v3/assert" ) @@ -48,11 +47,17 @@ func TestLogger(t *testing.T) { t.Parallel() for i := range logawl.AllLevels { - // only test non-exiting log levels switch i { + case 0: + fn := func() { + logger.Error("Test", "E") + } + var buffer bytes.Buffer + logger.Out = &buffer + fn() case 1: fn := func() { - logger.Info("") + logger.Warn("Test") } var buffer bytes.Buffer logger.Out = &buffer @@ -67,6 +72,7 @@ func TestLogger(t *testing.T) { case 3: fn := func() { logger.Debug("Test") + logger.Debug("Test 2") } var buffer bytes.Buffer logger.Out = &buffer @@ -79,6 +85,6 @@ func TestFmt(t *testing.T) { t.Parallel() ti := time.Now() test := []byte("test") - assert.ErrorContains(t, logger.FormatHeader(&test, ti, 0, 9001), "invalid log level") //make sure error is error - + // make sure error is error + assert.ErrorContains(t, logger.FormatHeader(&test, ti, 0, 9001), "invalid log level") } diff --git a/main_test.go b/main_test.go index 46aa256..6091ce8 100644 --- a/main_test.go +++ b/main_test.go @@ -5,9 +5,15 @@ package main import ( "os" "testing" + + "gotest.tools/v3/assert" ) +// nolint: paralleltest func TestMain(t *testing.T) { os.Args = []string{"awl", "+yaml", "@1.1.1.1"} main() + os.Args = []string{"awl", "+short", "@1.1.1.1"} + main() + assert.Assert(t, 1 == 2-1) } diff --git a/query/DNSCrypt.go b/query/DNSCrypt.go index 12751f8..70e5151 100644 --- a/query/DNSCrypt.go +++ b/query/DNSCrypt.go @@ -3,11 +3,11 @@ package query import ( + "fmt" "time" "git.froth.zone/sam/awl/cli" "git.froth.zone/sam/awl/internal/helpers" - "github.com/ameshkov/dnscrypt/v2" "github.com/miekg/dns" ) @@ -16,8 +16,8 @@ type DNSCryptResolver struct { opts cli.Options } +// LookUp performs a DNS query. func (r *DNSCryptResolver) LookUp(msg *dns.Msg) (helpers.Response, error) { - client := dnscrypt.Client{ Timeout: r.opts.Request.Timeout, UDPSize: 1232, @@ -39,7 +39,7 @@ func (r *DNSCryptResolver) LookUp(msg *dns.Msg) (helpers.Response, error) { resolverInf, err := client.Dial(r.opts.Request.Server) if err != nil { - return helpers.Response{}, err + return helpers.Response{}, fmt.Errorf("dnscrypt: dial error: %w", err) } now := time.Now() @@ -47,7 +47,7 @@ func (r *DNSCryptResolver) LookUp(msg *dns.Msg) (helpers.Response, error) { rtt := time.Since(now) if err != nil { - return helpers.Response{}, err + return helpers.Response{}, fmt.Errorf("dnscrypt: exchange error: %w", err) } r.opts.Logger.Info("Request successful") diff --git a/query/DNSCrypt_test.go b/query/DNSCrypt_test.go index 126a073..d49bb30 100644 --- a/query/DNSCrypt_test.go +++ b/query/DNSCrypt_test.go @@ -9,12 +9,12 @@ import ( "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 TestDNSCrypt(t *testing.T) { + t.Parallel() tests := []struct { opt cli.Options }{ @@ -42,11 +42,30 @@ func TestDNSCrypt(t *testing.T) { }, }, }, + { + cli.Options{ + Logger: util.InitLogger(0), + DNSCrypt: true, + TCP: true, + IPv4: true, + Request: helpers.Request{ + Server: "QMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", + Type: dns.TypeAAAA, + Name: "example.com.", + }, + }, + }, } for _, test := range tests { - res, err := query.CreateQuery(test.opt) - assert.NilError(t, err) - assert.Assert(t, res != helpers.Response{}) + test := test + t.Run("", func(t *testing.T) { + t.Parallel() + res, err := query.CreateQuery(test.opt) + if err == nil { + assert.Assert(t, res != helpers.Response{}) + } else { + assert.ErrorContains(t, err, "unsupported stamp") + } + }) } - } diff --git a/query/HTTPS.go b/query/HTTPS.go index b15dd81..91c23f4 100644 --- a/query/HTTPS.go +++ b/query/HTTPS.go @@ -18,6 +18,7 @@ type HTTPSResolver struct { opts cli.Options } +// LookUp performs a DNS query. func (r *HTTPSResolver) LookUp(msg *dns.Msg) (helpers.Response, error) { var resp helpers.Response httpR := &http.Client{ @@ -25,13 +26,13 @@ func (r *HTTPSResolver) LookUp(msg *dns.Msg) (helpers.Response, error) { } buf, err := msg.Pack() if err != nil { - return helpers.Response{}, err + return helpers.Response{}, fmt.Errorf("doh: packing error error: %w", err) } r.opts.Logger.Debug("making DoH request") - // query := server + "?dns=" + base64.RawURLEncoding.EncodeToString(buf) + // req, err := http.NewRequest("POST", r.opts.Request.Server, bytes.NewBuffer(buf)) if err != nil { - return helpers.Response{}, fmt.Errorf("DoH: %w", err) + return helpers.Response{}, fmt.Errorf("doh request creation error: %w", err) } req.Header.Set("Content-Type", "application/dns-message") req.Header.Set("Accept", "application/dns-message") @@ -41,24 +42,32 @@ func (r *HTTPSResolver) LookUp(msg *dns.Msg) (helpers.Response, error) { resp.RTT = time.Since(now) if err != nil { - return helpers.Response{}, fmt.Errorf("DoH HTTP request error: %w", err) + return helpers.Response{}, fmt.Errorf("doh HTTP request error: %w", err) } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return helpers.Response{}, fmt.Errorf("DoH server responded with HTTP %d", res.StatusCode) + return helpers.Response{}, &errHTTPStatus{res.StatusCode} } fullRes, err := io.ReadAll(res.Body) if err != nil { - return helpers.Response{}, fmt.Errorf("DoH body read error: %w", err) + return helpers.Response{}, fmt.Errorf("doh body read error: %w", err) } resp.DNS = &dns.Msg{} r.opts.Logger.Debug("unpacking response") err = resp.DNS.Unpack(fullRes) if err != nil { - return helpers.Response{}, fmt.Errorf("DoH dns message unpack error: %w", err) + return helpers.Response{}, fmt.Errorf("doh dns message unpack error: %w", err) } return resp, nil } + +type errHTTPStatus struct { + code int +} + +func (e *errHTTPStatus) Error() string { + return fmt.Sprintf("doh server responded with HTTP %d", e.code) +} diff --git a/query/HTTPS_test.go b/query/HTTPS_test.go index b6efae6..ee552a0 100644 --- a/query/HTTPS_test.go +++ b/query/HTTPS_test.go @@ -11,7 +11,6 @@ import ( "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" ) diff --git a/query/QUIC.go b/query/QUIC.go index e9e5dd1..10bf474 100644 --- a/query/QUIC.go +++ b/query/QUIC.go @@ -4,12 +4,12 @@ package query import ( "crypto/tls" + "fmt" "io" "time" "git.froth.zone/sam/awl/cli" "git.froth.zone/sam/awl/internal/helpers" - "github.com/lucas-clemente/quic-go" "github.com/miekg/dns" ) @@ -18,6 +18,7 @@ type QUICResolver struct { opts cli.Options } +// LookUp performs a DNS query. func (r *QUICResolver) LookUp(msg *dns.Msg) (helpers.Response, error) { var resp helpers.Response tls := &tls.Config{ @@ -31,46 +32,46 @@ func (r *QUICResolver) LookUp(msg *dns.Msg) (helpers.Response, error) { r.opts.Logger.Debug("making DoQ request") connection, err := quic.DialAddr(r.opts.Request.Server, tls, conf) if err != nil { - return helpers.Response{}, err + return helpers.Response{}, fmt.Errorf("doq: dial error: %w", err) } // Compress request to over-the-wire buf, err := msg.Pack() if err != nil { - return helpers.Response{}, err + return helpers.Response{}, fmt.Errorf("doq: pack error: %w", err) } t := time.Now() stream, err := connection.OpenStream() if err != nil { - return helpers.Response{}, err + return helpers.Response{}, fmt.Errorf("doq: quic stream creation error: %w", err) } _, err = stream.Write(buf) if err != nil { - return helpers.Response{}, err + return helpers.Response{}, fmt.Errorf("doq: quic stream write error: %w", err) } fullRes, err := io.ReadAll(stream) if err != nil { - return helpers.Response{}, err + return helpers.Response{}, fmt.Errorf("doq: quic stream read error: %w", err) } resp.RTT = time.Since(t) // Close with error: no error err = connection.CloseWithError(0, "") if err != nil { - return helpers.Response{}, err + return helpers.Response{}, fmt.Errorf("doq: quic connection close error: %w", err) } err = stream.Close() if err != nil { - return helpers.Response{}, err + return helpers.Response{}, fmt.Errorf("doq: quic stream close error: %w", err) } resp.DNS = &dns.Msg{} r.opts.Logger.Debug("unpacking DoQ response") err = resp.DNS.Unpack(fullRes) if err != nil { - return helpers.Response{}, err + return helpers.Response{}, fmt.Errorf("doq: upack error: %w", err) } return resp, nil } diff --git a/query/QUIC_test.go b/query/QUIC_test.go index b8837d1..a02d930 100644 --- a/query/QUIC_test.go +++ b/query/QUIC_test.go @@ -14,7 +14,6 @@ import ( "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" ) diff --git a/query/general.go b/query/general.go index 68a5f8d..eca10c1 100644 --- a/query/general.go +++ b/query/general.go @@ -8,7 +8,6 @@ import ( "git.froth.zone/sam/awl/cli" "git.froth.zone/sam/awl/internal/helpers" - "github.com/miekg/dns" ) @@ -16,6 +15,7 @@ type StandardResolver struct { opts cli.Options } +// LookUp performs a DNS query func (r *StandardResolver) LookUp(msg *dns.Msg) (helpers.Response, error) { var ( resp helpers.Response @@ -45,7 +45,7 @@ func (r *StandardResolver) LookUp(msg *dns.Msg) (helpers.Response, error) { resp.DNS, resp.RTT, err = dnsClient.Exchange(msg, r.opts.Request.Server) if err != nil { - return helpers.Response{}, err + return helpers.Response{}, fmt.Errorf("standard: DNS exchange error: %w", err) } r.opts.Logger.Info("Request successful") @@ -55,11 +55,14 @@ func (r *StandardResolver) LookUp(msg *dns.Msg) (helpers.Response, error) { switch { case r.opts.IPv4: dnsClient.Net += "4" - case r.opts.IPv4: + case r.opts.IPv6: dnsClient.Net += "6" } resp.DNS, resp.RTT, err = dnsClient.Exchange(msg, r.opts.Request.Server) } + if err != nil { + return helpers.Response{}, fmt.Errorf("standard: DNS exchange error: %w", err) + } - return resp, err + return resp, nil } diff --git a/query/general_test.go b/query/general_test.go index 5b080b9..7346e63 100644 --- a/query/general_test.go +++ b/query/general_test.go @@ -4,6 +4,7 @@ package query_test import ( "testing" + "time" "git.froth.zone/sam/awl/cli" "git.froth.zone/sam/awl/internal/helpers" @@ -14,25 +15,28 @@ import ( ) 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.", + Server: "8.8.4.1", + Type: dns.TypeA, + Name: "example.com.", + Timeout: time.Second / 2, + Retries: 0, }, } resolver, err := query.LoadResolver(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{}) + _, err = resolver.LookUp(msg) + assert.ErrorContains(t, err, "timeout") } func TestTruncate(t *testing.T) { + t.Parallel() opts := cli.Options{ Logger: util.InitLogger(0), IPv4: true, @@ -53,6 +57,7 @@ func TestTruncate(t *testing.T) { } func TestResolveAgain(t *testing.T) { + t.Parallel() tests := []struct { opt cli.Options }{ @@ -79,11 +84,26 @@ func TestResolveAgain(t *testing.T) { }, }, }, + { + cli.Options{ + Logger: util.InitLogger(0), + TLS: true, + Port: 853, + Request: helpers.Request{ + Server: "dns.google", + Type: dns.TypeAAAA, + Name: "example.com.", + }, + }, + }, } for _, test := range tests { - res, err := query.CreateQuery(test.opt) - assert.NilError(t, err) - assert.Assert(t, res != helpers.Response{}) + test := test + t.Run("", func(t *testing.T) { + t.Parallel() + res, err := query.CreateQuery(test.opt) + assert.NilError(t, err) + assert.Assert(t, res != helpers.Response{}) + }) } - } diff --git a/query/print.go b/query/print.go index 337ccd6..f5cf4eb 100644 --- a/query/print.go +++ b/query/print.go @@ -5,19 +5,18 @@ package query import ( "encoding/json" "encoding/xml" + "errors" "fmt" "strconv" "strings" "time" "git.froth.zone/sam/awl/cli" - "golang.org/x/net/idna" - "github.com/miekg/dns" + "golang.org/x/net/idna" "gopkg.in/yaml.v3" ) -// func PrintSpecial(msg *dns.Msg, opts cli.Options) (string, error) { formatted, err := MakePrintable(msg, opts) if err != nil { @@ -40,10 +39,12 @@ func PrintSpecial(msg *dns.Msg, opts cli.Options) (string, error) { return string(yaml), err default: - return "", fmt.Errorf("special: no recognized format given") + return "", errInvalidFormat } } +// MakePrintable takes a DNS message and makes it nicer to be printed as JSON,YAML, +// and XML. Little is changed beyond naming func MakePrintable(msg *dns.Msg, opts cli.Options) (*Message, error) { var err error ret := Message{ @@ -55,7 +56,7 @@ func MakePrintable(msg *dns.Msg, opts cli.Options) (*Message, error) { if opts.Display.UcodeTranslate { name, err = idna.ToUnicode(question.Name) if err != nil { - return nil, err + return nil, fmt.Errorf("punycode: error translating to unicode: %w", err) } } else { name = question.Name @@ -83,7 +84,7 @@ func MakePrintable(msg *dns.Msg, opts cli.Options) (*Message, error) { if opts.Display.UcodeTranslate { name, err = idna.ToUnicode(answer.Header().Name) if err != nil { - return nil, err + return nil, fmt.Errorf("punycode: error translating to unicode: %w", err) } } else { name = answer.Header().Name @@ -98,7 +99,6 @@ func MakePrintable(msg *dns.Msg, opts cli.Options) (*Message, error) { }, Value: temp[len(temp)-1], }) - } for _, ns := range msg.Ns { @@ -117,7 +117,7 @@ func MakePrintable(msg *dns.Msg, opts cli.Options) (*Message, error) { if opts.Display.UcodeTranslate { name, err = idna.ToUnicode(ns.Header().Name) if err != nil { - return nil, err + return nil, fmt.Errorf("punycode: error translating to unicode: %w", err) } } else { name = ns.Header().Name @@ -153,7 +153,7 @@ func MakePrintable(msg *dns.Msg, opts cli.Options) (*Message, error) { if opts.Display.UcodeTranslate { name, err = idna.ToUnicode(additional.Header().Name) if err != nil { - return nil, err + return nil, fmt.Errorf("punycode: error translating to unicode: %w", err) } } else { name = additional.Header().Name @@ -173,3 +173,5 @@ func MakePrintable(msg *dns.Msg, opts cli.Options) (*Message, error) { return &ret, nil } + +var errInvalidFormat = errors.New("this should never happen") diff --git a/query/print_test.go b/query/print_test.go new file mode 100644 index 0000000..732cb69 --- /dev/null +++ b/query/print_test.go @@ -0,0 +1,343 @@ +// 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 TestBadFormat(t *testing.T) { + t.Parallel() + _, err := query.PrintSpecial(new(dns.Msg), cli.Options{}) + assert.ErrorContains(t, err, "never happen") +} + +func TestPrinting(t *testing.T) { + t.Parallel() + opts := cli.Options{ + Logger: util.InitLogger(0), + Port: 53, + IPv4: false, + IPv6: false, + TCP: true, + DNSCrypt: false, + TLS: false, + HTTPS: false, + QUIC: false, + Truncate: false, + ShowQuery: true, + AA: false, + AD: false, + CD: false, + QR: false, + RD: true, + RA: false, + TC: false, + Z: false, + Reverse: false, + Verbosity: 0, + HumanTTL: false, + ShowTTL: true, + Short: false, + Identify: false, + JSON: true, + XML: false, + YAML: false, + Display: cli.Displays{ + Comments: true, + Question: true, + Answer: true, + Authority: true, + Additional: true, + Statistics: true, + UcodeTranslate: true, + }, + Request: helpers.Request{ + Server: "a.gtld-servers.net", + Type: dns.StringToType["NS"], + Class: 1, + Name: "google.com.", + Timeout: 0, + Retries: 0, + }, + EDNS: cli.EDNS{ + EnableEDNS: false, + }, + } + + resp, err := query.CreateQuery(opts) + assert.NilError(t, err) + + str, err := query.PrintSpecial(resp.DNS, opts) + assert.NilError(t, err) + assert.Assert(t, str != "") +} + +func TestPrinting2(t *testing.T) { + t.Parallel() + opts := cli.Options{ + Logger: util.InitLogger(0), + Port: 53, + IPv4: false, + IPv6: false, + TCP: true, + DNSCrypt: false, + TLS: false, + HTTPS: false, + QUIC: false, + Truncate: false, + ShowQuery: true, + AA: false, + AD: false, + CD: false, + QR: false, + RD: true, + RA: false, + TC: false, + Z: false, + Reverse: false, + Verbosity: 0, + HumanTTL: false, + ShowTTL: true, + Short: true, + Identify: true, + JSON: false, + XML: false, + YAML: true, + Display: cli.Displays{ + Comments: true, + Question: true, + Answer: true, + Authority: true, + Additional: true, + Statistics: true, + UcodeTranslate: true, + }, + Request: helpers.Request{ + Server: "ns1.google.com", + Type: dns.StringToType["NS"], + Class: 1, + Name: "google.com.", + Timeout: 0, + Retries: 0, + }, + EDNS: cli.EDNS{ + EnableEDNS: false, + }, + } + + resp, err := query.CreateQuery(opts) + assert.NilError(t, err) + + str, err := query.PrintSpecial(resp.DNS, opts) + assert.NilError(t, err) + assert.Assert(t, str != "") + + str = query.ToString(resp, opts) + assert.Assert(t, str != "") +} + +func TestPrinting3(t *testing.T) { + t.Parallel() + opts := cli.Options{ + Logger: util.InitLogger(0), + Port: 53, + IPv4: false, + IPv6: false, + TCP: false, + DNSCrypt: false, + TLS: false, + HTTPS: true, + QUIC: false, + Truncate: false, + ShowQuery: true, + AA: false, + AD: false, + CD: false, + QR: false, + RD: true, + RA: false, + TC: false, + Z: false, + Reverse: false, + Verbosity: 0, + HumanTTL: false, + ShowTTL: true, + Short: false, + Identify: true, + JSON: false, + XML: false, + YAML: true, + Display: cli.Displays{ + Comments: true, + Question: true, + Answer: true, + Authority: true, + Additional: true, + Statistics: true, + UcodeTranslate: true, + }, + Request: helpers.Request{ + Server: "https://dns.froth.zone/dns-query", + Type: dns.StringToType["NS"], + Class: 1, + Name: "freecumextremist.com.", + Timeout: 0, + Retries: 0, + }, + EDNS: cli.EDNS{ + EnableEDNS: false, + }, + } + + resp, err := query.CreateQuery(opts) + assert.NilError(t, err) + + str, err := query.PrintSpecial(resp.DNS, opts) + assert.NilError(t, err) + assert.Assert(t, str != "") + + str = query.ToString(resp, opts) + assert.Assert(t, str != "") +} + +func TestPrinting4(t *testing.T) { + t.Parallel() + opts := cli.Options{ + Logger: util.InitLogger(0), + Port: 853, + IPv4: false, + IPv6: false, + TCP: false, + DNSCrypt: false, + TLS: true, + HTTPS: false, + QUIC: false, + Truncate: false, + ShowQuery: true, + AA: false, + AD: false, + CD: false, + QR: false, + RD: true, + RA: false, + TC: false, + Z: false, + Reverse: false, + Verbosity: 0, + HumanTTL: false, + ShowTTL: true, + Short: false, + Identify: true, + JSON: false, + XML: false, + YAML: true, + Display: cli.Displays{ + Comments: true, + Question: true, + Answer: true, + Authority: true, + Additional: true, + Statistics: true, + UcodeTranslate: true, + }, + Request: helpers.Request{ + Server: "dns.google", + Type: dns.StringToType["NS"], + Class: 1, + Name: "freecumextremist.com.", + Timeout: 0, + Retries: 0, + }, + EDNS: cli.EDNS{ + EnableEDNS: false, + }, + } + + resp, err := query.CreateQuery(opts) + assert.NilError(t, err) + + str, err := query.PrintSpecial(resp.DNS, opts) + assert.NilError(t, err) + assert.Assert(t, str != "") + + str = query.ToString(resp, opts) + assert.Assert(t, str != "") +} + +func TestPrinting5(t *testing.T) { + t.Parallel() + opts := cli.Options{ + Logger: util.InitLogger(0), + Port: 53, + IPv4: false, + IPv6: false, + TCP: true, + DNSCrypt: false, + TLS: false, + HTTPS: false, + QUIC: false, + Truncate: false, + ShowQuery: true, + AA: true, + AD: false, + CD: false, + QR: false, + RD: true, + RA: false, + TC: false, + Z: false, + Reverse: false, + Verbosity: 0, + HumanTTL: false, + ShowTTL: true, + Short: false, + Identify: false, + JSON: false, + XML: false, + YAML: true, + Display: cli.Displays{ + Comments: true, + Question: true, + Answer: true, + Authority: true, + Additional: true, + Statistics: true, + UcodeTranslate: true, + }, + Request: helpers.Request{ + Server: "rin.froth.zone", + Type: dns.StringToType["A"], + Class: 1, + Name: "froth.zone.", + Timeout: 0, + Retries: 0, + }, + EDNS: cli.EDNS{ + EnableEDNS: true, + Cookie: true, + }, + } + + resp, err := query.CreateQuery(opts) + assert.NilError(t, err) + + str, err := query.PrintSpecial(resp.DNS, opts) + assert.NilError(t, err) + assert.Assert(t, str != "") + + str = query.ToString(resp, opts) + assert.Assert(t, str != "") +} + +func TestToString6(t *testing.T) { + assert.Assert(t, query.ToString(*new(helpers.Response), *new(cli.Options)) == " MsgHdr") +} diff --git a/query/query.go b/query/query.go index 05e2c93..0373e56 100644 --- a/query/query.go +++ b/query/query.go @@ -8,7 +8,6 @@ import ( "git.froth.zone/sam/awl/cli" "git.froth.zone/sam/awl/internal/helpers" - "github.com/dchest/uniuri" "github.com/miekg/dns" ) @@ -30,7 +29,6 @@ func CreateQuery(opts cli.Options) (helpers.Response, error) { // EDNS time :) if opts.EDNS.EnableEDNS { - o := new(dns.OPT) o.Hdr.Name = "." o.Hdr.Rrtype = dns.TypeOPT @@ -81,12 +79,10 @@ func CreateQuery(opts cli.Options) (helpers.Response, error) { } req.Extra = append(req.Extra, o) - } else { - if opts.EDNS.DNSSEC { - req.SetEdns0(1232, true) - opts.Logger.Warn("DNSSEC implies EDNS, EDNS enabled") - opts.Logger.Info("DNSSEC enabled, UDP buffer set to 1232") - } + } else if opts.EDNS.DNSSEC { + req.SetEdns0(1232, true) + opts.Logger.Warn("DNSSEC implies EDNS, EDNS enabled") + opts.Logger.Info("DNSSEC enabled, UDP buffer set to 1232") } opts.Logger.Debug(req) @@ -112,7 +108,7 @@ func CreateQuery(opts cli.Options) (helpers.Response, error) { opts.Display.Statistics = temp str += "\n;; QUERY SIZE: " + strconv.Itoa(req.Len()) } - fmt.Println(str, "\n------") + fmt.Println(str) opts.ShowQuery = false } } @@ -123,5 +119,6 @@ func CreateQuery(opts cli.Options) (helpers.Response, error) { } opts.Logger.Info("Query successfully loaded") + //nolint:wrapcheck // Error wrapping not needed here return resolver.LookUp(req) } diff --git a/query/query_test.go b/query/query_test.go index d06c58a..6b6ee66 100644 --- a/query/query_test.go +++ b/query/query_test.go @@ -9,100 +9,92 @@ import ( "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, + t.Parallel() + in := []cli.Options{ + { + Logger: util.InitLogger(0), + Port: 53, + QR: false, + Z: true, + RD: false, + ShowQuery: true, + YAML: true, - Request: helpers.Request{ - Server: "8.8.4.4", - Type: dns.TypeA, - Name: "example.com.", + Request: helpers.Request{ + Server: "8.8.4.4", + Type: dns.TypeA, + Name: "example.com.", + }, + Display: cli.Displays{ + Comments: true, + Question: true, + Opt: true, + Answer: true, + Authority: true, + Additional: true, + Statistics: true, + UcodeTranslate: false, + }, + EDNS: cli.EDNS{ + EnableEDNS: true, + DNSSEC: true, + Cookie: true, + Expire: true, + KeepOpen: true, + Nsid: true, + }, }, - EDNS: cli.EDNS{ - EnableEDNS: true, - DNSSEC: true, - Cookie: true, - Expire: true, - KeepOpen: true, - Nsid: true, + { + Logger: util.InitLogger(0), + Port: 53, + QR: false, + Z: true, + RD: false, + ShowQuery: true, + XML: true, + + Request: helpers.Request{ + Server: "8.8.4.4", + Type: dns.TypeA, + Name: "example.com.", + }, + Display: cli.Displays{ + Comments: true, + Question: true, + Opt: true, + Answer: true, + Authority: true, + Additional: true, + Statistics: true, + UcodeTranslate: true, + }, + EDNS: cli.EDNS{ + EnableEDNS: false, + DNSSEC: false, + Cookie: false, + Expire: false, + KeepOpen: false, + Nsid: false, + }, }, } - res, err := query.CreateQuery(opts) - assert.NilError(t, err) - assert.Assert(t, res != helpers.Response{}) -} - -func TestCreateQr(t *testing.T) { - opts := cli.Options{ - Logger: util.InitLogger(0), - Port: 53, - QR: false, - Z: true, - RD: false, - ShowQuery: true, - - Request: helpers.Request{ - Server: "8.8.4.4", - Type: dns.TypeA, - Name: "example.com.", - }, - EDNS: cli.EDNS{ - EnableEDNS: false, - DNSSEC: true, - Cookie: false, - Expire: false, - KeepOpen: false, - Nsid: false, - }, - Display: cli.Displays{ - Comments: true, - Question: true, - Answer: true, - Authority: true, - Additional: true, - Statistics: true, - UcodeTranslate: true, - }, + for _, opt := range in { + opt := opt + t.Run("", func(t *testing.T) { + t.Parallel() + res, err := query.CreateQuery(opt) + assert.NilError(t, err) + assert.Assert(t, res != helpers.Response{}) + str, err := query.PrintSpecial(res.DNS, opt) + assert.NilError(t, err) + assert.Assert(t, str != "") + str = query.ToString(res, opt) + assert.Assert(t, str != "") + }) } - res, err := query.CreateQuery(opts) - assert.NilError(t, err) - assert.Assert(t, res != helpers.Response{}) -} - -func TestCreateQr2(t *testing.T) { - opts := cli.Options{ - Logger: util.InitLogger(0), - Port: 53, - QR: false, - Z: true, - RD: false, - ShowQuery: true, - XML: true, - - Request: helpers.Request{ - Server: "8.8.4.4", - Type: dns.TypeA, - Name: "example.com.", - }, - EDNS: cli.EDNS{ - EnableEDNS: false, - DNSSEC: false, - Cookie: false, - Expire: false, - KeepOpen: false, - Nsid: false, - }, - } - 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 7c035ee..51740e6 100644 --- a/query/resolver.go +++ b/query/resolver.go @@ -12,10 +12,12 @@ import ( "github.com/miekg/dns" ) +// Main resolver interface type Resolver interface { LookUp(*dns.Msg) (helpers.Response, error) } +// LoadResolver loads the respective resolver for performing a DNS query func LoadResolver(opts cli.Options) (Resolver, error) { switch { case opts.HTTPS: diff --git a/query/struct.go b/query/struct.go index f776331..b4388da 100644 --- a/query/struct.go +++ b/query/struct.go @@ -9,42 +9,44 @@ import ( "git.froth.zone/sam/awl/cli" "git.froth.zone/sam/awl/internal/helpers" - "github.com/miekg/dns" ) +// Overall DNS response message type Message struct { - Header dns.MsgHdr `json:",omitempty" xml:",omitempty" yaml:",omitempty"` - Question []Question `json:",omitempty" xml:",omitempty" yaml:",omitempty"` - Answer []Answer `json:",omitempty" xml:",omitempty" yaml:",omitempty"` - Ns []Answer `json:",omitempty" xml:",omitempty" yaml:",omitempty"` - Extra []Answer `json:",omitempty" xml:",omitempty" yaml:",omitempty"` + Header dns.MsgHdr `json:"header,omitempty" xml:"header,omitempty" yaml:",omitempty"` + Question []Question `json:"question,omitempty" xml:"question,omitempty" yaml:",omitempty"` + Answer []Answer `json:"answer,omitempty" xml:"answer,omitempty" yaml:",omitempty"` + Ns []Answer `json:"ns,omitempty" xml:"ns,omitempty" yaml:",omitempty"` + Extra []Answer `json:"extra,omitempty" xml:"extra,omitempty" yaml:",omitempty"` } +// DNS Query type Question struct { - Name string `json:",omitempty" xml:",omitempty" yaml:",omitempty"` - Type string `json:",omitempty" xml:",omitempty" yaml:",omitempty"` - Class string `json:",omitempty" xml:",omitempty" yaml:",omitempty"` + Name string `json:"name,omitempty" xml:"name,omitempty" yaml:",omitempty"` + Type string `json:"type,omitempty" xml:"type,omitempty" yaml:",omitempty"` + Class string `json:"class,omitempty" xml:"class,omitempty" yaml:",omitempty"` } +// DNS Resource Headers type RRHeader struct { - Name string `json:",omitempty" xml:",omitempty" yaml:",omitempty"` - Type string `json:",omitempty" xml:",omitempty" yaml:",omitempty"` - Class string `json:",omitempty" xml:",omitempty" yaml:",omitempty"` - TTL string `json:",omitempty" xml:",omitempty" yaml:",omitempty"` + Name string `json:"name,omitempty" xml:"name,omitempty" yaml:",omitempty"` + Type string `json:"type,omitempty" xml:"type,omitempty" yaml:",omitempty"` + Class string `json:"class,omitempty" xml:"class,omitempty" yaml:",omitempty"` + TTL string `json:"ttl,omitempty" xml:"ttl,omitempty" yaml:",omitempty"` Rdlength uint16 `json:"-" xml:"-" yaml:"-"` } +// DNS Response type Answer struct { - RRHeader `json:"Header,omitempty" xml:"Header,omitempty" yaml:"header,omitempty"` - Value string `json:"Response,omitempty" xml:"Response,omitempty" yaml:"response,omitempty"` + RRHeader `json:"header,omitempty" xml:"header,omitempty" yaml:"header,omitempty"` + Value string `json:"response,omitempty" xml:"response,omitempty" yaml:"response,omitempty"` } -// Turn the response into dig +// ToString turns the response into something that looks a lot like dig // // Much of this is taken from https://github.com/miekg/dns/blob/master/msg.go#L900 func ToString(res helpers.Response, opts cli.Options) string { - if res.DNS == nil { return " MsgHdr" } @@ -52,7 +54,6 @@ func ToString(res helpers.Response, opts cli.Options) string { var opt *dns.OPT if !opts.Short { - if opts.Display.Comments { s += res.DNS.MsgHdr.String() + " " s += "QUERY: " + strconv.Itoa(len(res.DNS.Question)) + ", " @@ -134,14 +135,18 @@ func ToString(res helpers.Response, opts cli.Options) string { } } else { // Print just the responses, nothing else - for _, res := range res.DNS.Answer { - temp := strings.Split(res.String(), "\t") + for i, resp := range res.DNS.Answer { + temp := strings.Split(resp.String(), "\t") s += temp[len(temp)-1] + if opts.Identify { + s += " from server " + opts.Request.Server + " in " + res.RTT.String() + } + // Don't print newline on last line + if i != len(res.DNS.Answer)-1 { + s += "\n" + } + } - if opts.Identify { - s += " from server " + opts.Request.Server + " in " + res.RTT.String() - } - // s += "\n" } return s diff --git a/template.mk b/template.mk index 57cfc31..93ffe84 100644 --- a/template.mk +++ b/template.mk @@ -24,15 +24,18 @@ doc/$(PROG).1: doc/wiki/$(PROG).1.md @cp doc/awl.1 doc/awl.bak $(SCDOC) doc/$(PROG).1 && rm doc/awl.bak || mv doc/awl.bak doc/awl.1 + ## test: run go test test: $(GO) test -cover -coverprofile=coverage/coverage.out ./... -## cover: generates test coverage, output as HTML -cover: test +coverage/coverage.out: test $(COVER) -func=coverage/coverage.out $(COVER) -html=coverage/coverage.out -o coverage/cover.html +## cover: generates test coverage, output as HTML +cover: coverage/coverage.out + fmt: gofmt -w -s . @@ -41,7 +44,7 @@ vet: ## lint: lint awl, using fmt, vet and golangci-lint lint: fmt vet - -golangci-lint run + -golangci-lint run --fix ## clean: clean the build files clean: diff --git a/util/logger.go b/util/logger.go index 037d4d7..1c4bb03 100644 --- a/util/logger.go +++ b/util/logger.go @@ -5,10 +5,10 @@ package util import "git.froth.zone/sam/awl/logawl" // Initialize the logawl instance. -func InitLogger(verbosity int) (Logger *logawl.Logger) { - Logger = logawl.New() +func InitLogger(verbosity int) (logger *logawl.Logger) { + logger = logawl.New() - Logger.SetLevel(logawl.Level(verbosity)) + logger.SetLevel(logawl.Level(verbosity)) - return + return logger } diff --git a/util/logger_test.go b/util/logger_test.go index a9502be..0dce589 100644 --- a/util/logger_test.go +++ b/util/logger_test.go @@ -11,6 +11,7 @@ import ( ) func TestInitLogger(t *testing.T) { + t.Parallel() logger := util.InitLogger(0) assert.Equal(t, logger.Level, logawl.Level(0)) } diff --git a/util/reverseDNS.go b/util/reverseDNS.go index 57d800e..cfaef14 100644 --- a/util/reverseDNS.go +++ b/util/reverseDNS.go @@ -3,18 +3,29 @@ package util import ( - "errors" "fmt" "strings" "github.com/miekg/dns" ) +type errReverseDNS struct { + addr string +} + +func (e *errReverseDNS) Error() string { + return fmt.Sprintf("reverseDNS: invalid value %s given", e.addr) +} + // 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" { - return dns.ReverseAddr(address) + str, err := dns.ReverseAddr(address) + if err != nil { + return "", fmt.Errorf("could not reverse: %w", err) + } + return str, nil } else if query == "NAPTR" { // get rid of characters not needed replacer := strings.NewReplacer("+", "", " ", "", "-", "") @@ -30,7 +41,7 @@ func ReverseDNS(address string, querInt uint16) (string, error) { return arpa.String(), nil } - return "", errors.New("ReverseDNS: -x flag given but no IP found") + return "", &errReverseDNS{address} } // Reverse a string, return the string in reverse. diff --git a/util/reverseDNS_test.go b/util/reverseDNS_test.go index 7841e40..6e2fae6 100644 --- a/util/reverseDNS_test.go +++ b/util/reverseDNS_test.go @@ -6,7 +6,6 @@ import ( "testing" "git.froth.zone/sam/awl/util" - "github.com/miekg/dns" "gotest.tools/v3/assert" ) @@ -56,5 +55,11 @@ func TestNAPTR(t *testing.T) { func TestInvalid(t *testing.T) { t.Parallel() _, err := util.ReverseDNS("AAAAA", 1) - assert.ErrorContains(t, err, "no IP found") + assert.ErrorContains(t, err, "invalid value AAAAA given") +} + +func TestInvalid2(t *testing.T) { + t.Parallel() + _, err := util.ReverseDNS("1.0", PTR) + assert.ErrorContains(t, err, "could not reverse") }