diff --git a/.drone.jsonnet b/.drone.jsonnet index 7ac857c..b769190 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -1,22 +1,68 @@ -local pipeline(version, arch) = { +// SPDX-License-Identifier: BSD-3-Clause + +local testing(version, arch) = { kind: "pipeline", name: version + "-" + arch , platform: { arch: arch }, steps: [ + { + name: "lint", + image: "rancher/drone-golangci-lint:latest" + }, { name: "test", image: "golang:" + version, commands: [ - "go test ./..." + "go test -race ./... -cover" ] + }, + ] +}; + +// "Inspired by" https://goreleaser.com/ci/drone/ +local release() = { + kind: "pipeline", + name: "release", + trigger: { + event: "tag" + }, + steps: [ + { + name: "fetch", + image: "docker:git", + commands : [ + "git fetch --tags" + ] + }, + { + name: "test", + image: "golang", + commands: [ + "go test -race ./... -cover" + ] + }, + { + name: "release", + image: "goreleaser/goreleaser", + environment: { + "GITEA_TOKEN": { + from_secret: "GITEA_TOKEN" + } + }, + commands: [ + "goreleaser release" + ], + // when: { + // event: "tag" + // } } ] }; -// logawl uses generics so 1.18 is the minimum [ - pipeline("1.18", "amd64"), - pipeline("1.18", "arm64"), + testing("1.18", "amd64"), + testing("1.18", "arm64"), + release() ] \ No newline at end of file diff --git a/.gitignore b/.gitignore index 682e852..be06a1f 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,9 @@ # vendor/ # Go workspace file -go.work \ No newline at end of file +go.work +dist/ + +# Test coverage +coverage/* +!coverage/.gitkeep diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..1b94379 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "doc/wiki"] + path = doc/wiki + url = ../awl.wiki diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..88b95e2 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,39 @@ +# This is an example .goreleaser.yml file with some sensible defaults. +# Make sure to check the documentation at https://goreleaser.com +before: + hooks: + # You may remove this if you don't use go modules. + - go mod tidy + # you may remove this if you don't need go generate + # - go generate ./... +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + goarch: + - amd64 + - arm64 +universal_binaries: +- replace: true +archives: + - replacements: + darwin: macOS + linux: Linux + windows: Windows + amd64: x86_64 + format_overrides: + - goos: windows + format: zip +checksum: + name_template: 'checksums.txt' +snapshot: + name_template: "{{ incpatch .Version }}-next" +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..7abf056 --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: BSD-3-Clause + +include template.mk + +ifeq ($(OS),Windows_NT) + EXE := $(PROG).exe +else + EXE := $(PROG) +endif + + +$(PROG): + $(GO) build -o $(EXE) $(GOFLAGS) . + +## install: installs awl +install: all +ifeq ($(OS),Windows_NT) + $(GO) install $(GOFLAGS) . +else + install -m755 $(PROG) $(PREFIX)/$(BIN) + install -m644 doc/$(PROG).1 $(MAN)/man1 +endif + +.PHONY: install \ No newline at end of file diff --git a/LICENCE b/LICENSE similarity index 100% rename from LICENCE rename to LICENSE diff --git a/Makefile b/Makefile index f2ca423..d45f0fd 100644 --- a/Makefile +++ b/Makefile @@ -1,27 +1,14 @@ -GO:=go -GOFLAGS:= -ldflags '-s -w' -PREFIX:=/usr/local -BINPATH=$(PREFIX)/bin +# SPDX-License-Identifier: BSD-3-Clause +# BSD/POSIX makefile -# hehe -all: awl +include template.mk -awl: . - $(GO) build -o awl $(GOFLAGS) . +$(PROG): + $(GO) build -o $(PROG) $(GOFLAGS) . -test: - $(GO) test ./... +## install: installs awl +install: all + install -m755 $(PROG) $(PREFIX)/$(BIN) + install -m644 doc/$(PROG).1 $(MAN)/man1 -fmt: - $(GO) fmt - -vet: - $(GO) vet - -lint: fmt vet - -install: awl - install awl $(BINPATH) || echo "You probably need to run `sudo make install`" - -clean: - $(GO) clean \ No newline at end of file +.PHONY: install \ No newline at end of file diff --git a/Mkfile b/Mkfile deleted file mode 100644 index c8a0359..0000000 --- a/Mkfile +++ /dev/null @@ -1,9 +0,0 @@ -GO=GO -awl: awl.go - $GO build -o awl - -install: - $GO install . - -test: - $GO test ./... diff --git a/README.md b/README.md index 7e37b9c..c1ca76f 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,66 @@ -# awl - -`awl` is a command-line DNS client, much like -[`drill`](https://github.com/NLnetLabs/ldns), -[`dig`](https://bind9.readthedocs.io/en/v9_18_3/manpages.html#dig-dns-lookup-utility), -[`dog`](https://github.com/ogham/dog), -[`doggo`](https://github.com/mr-karan/doggo), -or [`q`](https://github.com/natesales/q) - -This was made as my first major experiment with Go, so there are probably things that can be improved - -The excellent [dns](https://github.com/miekg/dns) library for Go does most of the heavy -lifting. - -## What works - -- UDP -- TCP -- TLS -- HTTPS (maybe) -- QUIC (extreme maybe) - -## What doesn't - -- Your sanity after reading my awful code -- A motivation for making this after finding q and doggo - -## What should change - -- Make the CLI less abysmal (migrate to [cobra](https://github.com/spf13/cobra)? - or just use stdlib's flags) -- Optimize everything -- Make the code less spaghetti (partially completed) -- Feature parity with drill - - Making a drop-in replacement for drill? \ No newline at end of file +# awl + +[![Build Status](https://ci.git.froth.zone/api/badges/sam/awl/status.svg)](https://ci.git.froth.zone/sam/awl) + +`awl` is a command-line DNS client, much like +[`drill`](https://github.com/NLnetLabs/ldns), +[`dig`](https://bind9.readthedocs.io/en/v9_18_3/manpages.html#dig-dns-lookup-utility), +[`dog`](https://github.com/ogham/dog), +[`doggo`](https://github.com/mr-karan/doggo), or +[`q`](https://github.com/natesales/q). + +`awl` is designed to be a drop-in replacement for the venerable dig, but support +newer RFC query types, such as DNS-over-HTTPS and DNS-over-QUIC. + +## Usage + +- [Feature wiki](https://git.froth.zone/sam/awl/wiki/Supported) +- [Manpage](https://git.froth.zone/sam/awl/wiki/awl.1) + + +## Building and installing + +### From releases + +Grab a prebuilt binary from the +[release](https://git.froth.zone/sam/awl/releases) section. + +### From source + +Dependencies: + +- Go >= 1.18 +- GNU/BSD make or Plan 9 mk +- [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (optional, for manpage) + +Make sure to recursively clone the repo: + +```sh +git clone --recursive https://git.froth.zone/sam/awl +``` + +Using the makefile: + +```sh +make +sudo make install +``` + +Alternatively, using `go install`: + +```sh +go install git.froth.zone/sam/awl@latest +``` + +## Contributing + +Send a [pull request](https://git.froth.zone/sam/awl/pulls) our way. Prefer +emails? Send a patch to the +[mailing list](https://lists.sr.ht/~sammefishe/awl-dev). + +Found a bug or want a new feature? Create an issue +[here](https://git.froth.zone/sam/awl/issues). + +### License + +See [LICENSE](./LICENSE) diff --git a/awl.go b/awl.go deleted file mode 100644 index 6c5af9c..0000000 --- a/awl.go +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause - -package main - -import ( - "fmt" - "os" -) - -func main() { - app := prepareCLI() - err := app.Run(os.Args) - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } -} diff --git a/cli.go b/cli.go deleted file mode 100644 index e810161..0000000 --- a/cli.go +++ /dev/null @@ -1,216 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause - -package main - -import ( - "fmt" - "math/rand" - "runtime" - "strings" - - "git.froth.zone/sam/awl/conf" - "git.froth.zone/sam/awl/query" - - "github.com/miekg/dns" - "github.com/urfave/cli/v2" - "golang.org/x/net/idna" -) - -// Do all the magic CLI crap -func prepareCLI() *cli.App { - // Custom version string - cli.VersionPrinter = func(c *cli.Context) { - fmt.Printf("%s version %s, built with %s\n", c.App.Name, c.App.Version, runtime.Version()) - } - - cli.VersionFlag = &cli.BoolFlag{ - Name: "v", - Usage: "show version and exit", - } - - cli.HelpFlag = &cli.BoolFlag{ - Name: "h", - Usage: "show this help and exit", - } - - // Hack to get rid of the annoying default on the CLI - oldFlagStringer := cli.FlagStringer - cli.FlagStringer = func(f cli.Flag) string { - return strings.TrimSuffix(oldFlagStringer(f), " (default: false)") - } - - cli.AppHelpTemplate = `{{.Name}} - {{.Usage}} - - Usage: {{.HelpName}} name [@server] [record] - can be a name or an IP address - defaults to A - - arguments can be in any order - {{if .VisibleFlags}} - Options: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}}` - app := &cli.App{ - Name: "awl", - Usage: "drill, writ small", - Version: "v0.2.1", - Flags: []cli.Flag{ - &cli.IntFlag{ - Name: "port", - Aliases: []string{"p"}, - Usage: "`` to make DNS query", - DefaultText: "53 over plain TCP/UDP, 853 over TLS or QUIC", - }, - &cli.BoolFlag{ - Name: "4", - Usage: "force IPv4", - }, - &cli.BoolFlag{ - Name: "6", - Usage: "force IPv6", - }, - &cli.BoolFlag{ - Name: "dnssec", - Aliases: []string{"D"}, - Usage: "enable DNSSEC", - }, - &cli.BoolFlag{ - Name: "json", - Aliases: []string{"j"}, - Usage: "return the result(s) as JSON", - }, - &cli.BoolFlag{ - Name: "short", - Aliases: []string{"s"}, - Usage: "print just the results, equivalent to dig +short", - }, - &cli.BoolFlag{ - Name: "tcp", - Aliases: []string{"t"}, - Usage: "use TCP (default: use UDP)", - }, - &cli.BoolFlag{ - Name: "tls", - Aliases: []string{"T"}, - Usage: "use DNS-over-TLS", - }, - &cli.BoolFlag{ - Name: "https", - Aliases: []string{"H"}, - Usage: "use DNS-over-HTTPS", - }, - &cli.BoolFlag{ - Name: "quic", - Aliases: []string{"Q"}, - Usage: "use DNS-over-QUIC", - }, - &cli.BoolFlag{ - Name: "no-truncate", - Usage: "ignore truncation if a UDP request truncates (default: retry with TCP)", - }, - &cli.BoolFlag{ - Name: "aa", - Usage: "set AA (Authoratative Answer) flag (default: not set)", - }, - &cli.BoolFlag{ - Name: "tc", - Usage: "set TC (TrunCated) flag (default: not set)", - }, - &cli.BoolFlag{ - Name: "z", - Usage: "set Z (Zero) flag (default: not set)", - }, - &cli.BoolFlag{ - Name: "cd", - Usage: "set CD (Checking Disabled) flag (default: not set)", - }, - &cli.BoolFlag{ - Name: "no-rd", - Usage: "UNset RD (Recursion Desired) flag (default: set)", - }, - &cli.BoolFlag{ - Name: "no-ra", - Usage: "UNset RA (Recursion Available) flag (default: set)", - }, - &cli.BoolFlag{ - Name: "reverse", - Aliases: []string{"x"}, - Usage: "do a reverse lookup", - }, - &cli.BoolFlag{ - Name: "debug", - Usage: "enable verbose logging", - }, - }, - Action: doQuery, - } - return app -} - -// Parse the wildcard arguments, drill style -func parseArgs(args []string, opts query.Options) (query.Answers, error) { - var ( - resp query.Response - err error - ) - for _, arg := range args { - r, ok := dns.StringToType[strings.ToUpper(arg)] - switch { - // If it starts with @, it's a DNS server - case strings.HasPrefix(arg, "@"): - resp.Answers.Server = strings.Split(arg, "@")[1] - case strings.Contains(arg, "."): - resp.Answers.Name, err = idna.ToUnicode(arg) - if err != nil { - return query.Answers{}, err - } - case ok: - // If it's a DNS request, it's a DNS request (obviously) - resp.Answers.Request = r - default: - //else, assume it's a name - resp.Answers.Name, err = idna.ToUnicode(arg) - if err != nil { - return query.Answers{}, err - } - - } - } - - // If nothing was set, set a default - if resp.Answers.Name == "" { - resp.Answers.Name = "." - if resp.Answers.Request == 0 { - resp.Answers.Request = dns.StringToType["NS"] - } - } else { - if resp.Answers.Request == 0 { - resp.Answers.Request = dns.StringToType["A"] - } - } - if resp.Answers.Server == "" { - resolv, err := conf.GetDNSConfig() - if err != nil { // Query Google by default - resp.Answers.Server = "8.8.4.4" - } else { - for _, srv := range resolv.Servers { - if opts.IPv4 { - if strings.Contains(srv, ".") { - resp.Answers.Server = srv - break - } - } else if opts.IPv6 { - if strings.Contains(srv, ":") { - resp.Answers.Server = srv - break - } - } else { - resp.Answers.Server = resolv.Servers[rand.Intn(len(resolv.Servers))] - break - } - } - } - } - - return query.Answers{Server: resp.Answers.Server, Request: resp.Answers.Request, Name: resp.Answers.Name}, nil -} diff --git a/cli/cli.go b/cli/cli.go new file mode 100644 index 0000000..f14e0e2 --- /dev/null +++ b/cli/cli.go @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package cli + +import ( + "fmt" + "os" + "runtime" + "strings" + "time" + + "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. +func ParseCLI(version string) (Options, error) { + flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) + + flag.Usage = func() { + fmt.Println(`awl - drill, writ small + + Usage: awl name [@server] [record] + 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 + + Options:`) + flag.PrintDefaults() + } + + // 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` (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')) + reverse = flag.Bool("reverse", false, "do a reverse lookup", flag.OptShorthand('x')) + + timeout = flag.Float32("timeout", 1, "Timeout, in `seconds`") + retry = flag.Int("retries", 2, "number of `times` to retry") + dnssec = flag.Bool("dnssec", false, "enable DNSSEC", flag.OptShorthand('D')) + truncate = flag.Bool("no-truncate", false, "ignore truncation if a UDP request truncates (default= retry with TCP)") + + tcp = flag.Bool("tcp", false, "use TCP") + dnscrypt = flag.Bool("dnscrypt", false, "use DNSCrypt") + tls = flag.Bool("tls", false, "use DNS-over-TLS", flag.OptShorthand('T')) + https = flag.Bool("https", false, "use DNS-over-HTTPS", flag.OptShorthand('H')) + quic = flag.Bool("quic", false, "use DNS-over-QUIC", flag.OptShorthand('Q')) + + 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: 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: 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')) + + short = flag.Bool("short", false, "print just the results, equivalent to dig +short", flag.OptShorthand('s')) + json = flag.Bool("json", false, "print the result(s) as JSON", flag.OptShorthand('j')) + xml = flag.Bool("xml", false, "print the result(s) as XML", flag.OptShorthand('X')) + yaml = flag.Bool("yaml", false, "print the result(s) as yaml", flag.OptShorthand('y')) + + noQ = flag.Bool("no-question", false, "disable printing the question section") + 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") + noStats = flag.Bool("no-statistics", false, "disable printing the statistics section") + + verbosity = flag.Int("verbosity", 0, "sets verbosity `level`", flag.OptShorthand('v'), flag.OptNoOptDefVal("2")) + versionFlag = flag.Bool("version", false, "print version information", flag.OptShorthand('V')) + ) + + // Don't sort the flags when -h is given + flag.CommandLine.SortFlags = false + + // Parse the flags + err := flag.CommandLine.Parse(os.Args[1:]) + if err != nil { + return Options{Logger: util.InitLogger(*verbosity)}, err + } + + opts := Options{ + Logger: util.InitLogger(*verbosity), + Port: *port, + IPv4: *ipv4, + IPv6: *ipv6, + DNSSEC: *dnssec, + Short: *short, + TCP: *tcp, + DNSCrypt: *dnscrypt, + TLS: *tls, + HTTPS: *https, + QUIC: *quic, + Truncate: *truncate, + AA: *aa, + AD: *ad, + TC: *tc, + Z: *z, + CD: *cd, + QR: *qr, + RD: *rd, + RA: *ra, + Reverse: *reverse, + JSON: *json, + XML: *xml, + YAML: *yaml, + Request: helpers.Request{ + Type: dns.StringToType[strings.ToUpper(*qType)], + Class: dns.StringToClass[strings.ToUpper(*class)], + Name: *query, + Timeout: time.Duration(*timeout * float32(time.Second)), + Retries: *retry, + }, + Display: Displays{ + Question: !*noQ, + Answer: !*noAns, + Authority: !*noAuth, + Additional: !*noAdd, + Statistics: !*noStats, + }, + } + + opts.Logger.Info("POSIX flags parsed") + opts.Logger.Debug(fmt.Sprintf("%+v", opts)) + + if *versionFlag { + fmt.Printf("awl version %s, built with %s\n", version, runtime.Version()) + return opts, ErrNotError + } + + // Parse all the arguments that don't start with - or -- + // This includes the dig-style (+) options + err = ParseMiscArgs(flag.Args(), &opts) + if err != nil { + opts.Logger.Warn(err) + return opts, err + } + opts.Logger.Info("Dig/Drill flags parsed") + opts.Logger.Debug(fmt.Sprintf("%+v", opts)) + + if opts.Port == 0 { + if opts.TLS || opts.QUIC { + opts.Port = 853 + } else { + opts.Port = 53 + } + } + opts.Logger.Info("Port set to", opts.Port) + + // Set timeout to 0.5 seconds if set below 0.5 + if opts.Request.Timeout < (time.Second / 2) { + opts.Request.Timeout = (time.Second / 2) + } + + if opts.Request.Retries < 0 { + opts.Request.Retries = 0 + } + + return opts, nil +} diff --git a/cli/cli_test.go b/cli/cli_test.go new file mode 100644 index 0000000..21b9e63 --- /dev/null +++ b/cli/cli_test.go @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package cli_test + +import ( + "os" + "testing" + "time" + + "git.froth.zone/sam/awl/cli" + "gotest.tools/v3/assert" +) + +func TestEmpty(t *testing.T) { + old := os.Args + os.Args = []string{"awl", "-4"} + opts, err := cli.ParseCLI("TEST") + assert.NilError(t, err) + assert.Assert(t, (opts.Port == 53)) + assert.Assert(t, opts.IPv4) + os.Args = old +} + +func TestTLSPort(t *testing.T) { + old := os.Args + os.Args = []string{"awl", "-T"} + opts, err := cli.ParseCLI("TEST") + assert.NilError(t, err) + assert.Assert(t, (opts.Port == 853)) + os.Args = old +} + +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 TestInvalidDig(t *testing.T) { + old := os.Args + os.Args = []string{"awl", "+a"} + _, err := cli.ParseCLI("TEST") + assert.ErrorContains(t, err, "dig: unknown flag") + os.Args = old +} + +func TestVersion(t *testing.T) { + old := os.Args + os.Args = []string{"awl", "--version"} + _, err := cli.ParseCLI("TEST") + assert.ErrorType(t, err, cli.ErrNotError) + os.Args = old +} + +func TestTimeout(t *testing.T) { + args := [][]string{ + {"awl", "+timeout=0"}, + {"awl", "--timeout", "0"}, + } + for _, test := range args { + old := os.Args + os.Args = test + opt, err := cli.ParseCLI("TEST") + assert.NilError(t, err) + assert.Equal(t, opt.Request.Timeout, time.Second/2) + os.Args = old + } +} + +func TestRetries(t *testing.T) { + args := [][]string{ + {"awl", "+retry=-2"}, + {"awl", "+tries=-2"}, + {"awl", "--retries", "-2"}, + } + for _, test := range args { + old := os.Args + os.Args = test + opt, err := cli.ParseCLI("TEST") + assert.NilError(t, err) + assert.Equal(t, opt.Request.Retries, 0) + os.Args = old + } +} + +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") + }) +} diff --git a/cli/dig.go b/cli/dig.go new file mode 100644 index 0000000..32b4193 --- /dev/null +++ b/cli/dig.go @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package cli + +import ( + "fmt" + "strconv" + "strings" + "time" +) + +// 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") + opts.Logger.Info("Setting", arg) + + switch arg { + // Set DNS query flags + case "aa", "aaflag", "aaonly", "noaa", "noaaflag", "noaaonly": + opts.AA = isNo + case "ad", "adflag", "noad", "noadflag": + opts.AD = isNo + case "cd", "cdflag", "nocd", "nocdflag": + opts.CD = isNo + case "qr", "qrflag", "noqr", "noqrflag": + opts.QR = isNo + case "ra", "raflag", "nora", "noraflag": + opts.RA = isNo + case "rd", "rdflag", "recurse", "nord", "nordflag", "norecurse": + opts.RD = isNo + case "tc", "tcflag", "notc", "notcflag": + opts.TC = isNo + case "z", "zflag", "noz", "nozflag": + opts.Z = isNo + // End DNS query flags + + // DNS-over-X + case "dnssec", "nodnssec": + opts.DNSSEC = isNo + case "tcp", "vc", "notcp", "novc": + opts.TCP = isNo + case "ignore", "noignore": + opts.Truncate = isNo + case "tls", "notls": + opts.TLS = isNo + case "dnscrypt", "nodnscrypt": + opts.DNSCrypt = isNo + case "https", "nohttps": + opts.HTTPS = isNo + case "quic", "noquic": + opts.QUIC = isNo + // End DNS-over-X + + // Formatting + case "short", "noshort": + opts.Short = isNo + case "json", "nojson": + opts.JSON = isNo + case "xml", "noxml": + opts.XML = isNo + case "yaml", "noyaml": + opts.YAML = isNo + // End formatting + + // Output + // TODO: get this to work + // case "comments", "nocomments": + // opts.Display.Comments = isNo + case "question", "noquestion": + opts.Display.Question = isNo + case "answer", "noanswer": + opts.Display.Answer = isNo + case "authority", "noauthority": + opts.Display.Authority = isNo + case "additional", "noadditional": + opts.Display.Additional = isNo + case "stats", "nostats": + opts.Display.Statistics = isNo + + case "all", "noall": + opts.Display.Question = isNo + opts.Display.Answer = isNo + opts.Display.Authority = isNo + opts.Display.Additional = isNo + opts.Display.Statistics = isNo + + default: + // Recursive switch statements WOO + switch { + case strings.HasPrefix(arg, "timeout"): + timeout, err := strconv.Atoi(strings.Split(arg, "=")[1]) + + if err != nil { + return fmt.Errorf("dig: Invalid timeout value") + } + + opts.Request.Timeout = time.Duration(timeout) + + case strings.HasPrefix(arg, "retry"), strings.HasPrefix(arg, "tries"): + tries, err := strconv.Atoi(strings.Split(arg, "=")[1]) + if err != nil { + return fmt.Errorf("dig: Invalid retry value") + } + + if strings.HasPrefix(arg, "tries") { + tries++ + } + + opts.Request.Retries = tries + + default: + return fmt.Errorf("dig: unknown flag %s given", arg) + } + } + return nil +} diff --git a/cli/dig_test.go b/cli/dig_test.go new file mode 100644 index 0000000..4b12db4 --- /dev/null +++ b/cli/dig_test.go @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package cli_test + +import ( + "testing" + + "git.froth.zone/sam/awl/cli" + "git.froth.zone/sam/awl/util" + "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", + "tls", "notls", + "dnscrypt", "nodnscrypt", + "https", "nohttps", + "quic", "noquic", + "short", "noshort", + "json", "nojson", + "xml", "noxml", + "yaml", "noyaml", + "question", "noquestion", + "answer", "noanswer", + "authority", "noauthority", + "additional", "noadditional", + "stats", "nostats", + "all", "noall", + "invalid", + } + for _, tc := range seeds { + f.Add(tc) + } + + f.Fuzz(func(t *testing.T, orig string) { + opts := new(cli.Options) + opts.Logger = util.InitLogger(0) + err := cli.ParseDig(orig, opts) + if err != nil { + assert.ErrorContains(t, err, "unknown flag") + } + }) +} diff --git a/cli/docs.go b/cli/docs.go new file mode 100644 index 0000000..dbc972b --- /dev/null +++ b/cli/docs.go @@ -0,0 +1,5 @@ +/* +The CLI part of the package, including both POSIX +flag parsing and dig-like flag parsing. +*/ +package cli diff --git a/cli/misc.go b/cli/misc.go new file mode 100644 index 0000000..c2bbb94 --- /dev/null +++ b/cli/misc.go @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package cli + +import ( + "fmt" + "math/rand" + "strings" + + "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. +func ParseMiscArgs(args []string, opts *Options) error { + var err error + + for _, arg := range args { + r, ok := dns.StringToType[strings.ToUpper(arg)] + switch { + // If it starts with @, it's a DNS server + case strings.HasPrefix(arg, "@"): + arg = arg[1:] + // Automatically set flags based on URI header + opts.Logger.Info(arg, "detected as a server") + switch { + case strings.HasPrefix(arg, "tls://"): + opts.TLS = true + opts.Request.Server = arg[6:] + opts.Logger.Info("DNS-over-TLS implicitly set") + case strings.HasPrefix(arg, "https://"): + opts.HTTPS = true + opts.Request.Server = arg + opts.Logger.Info("DNS-over-HTTPS implicitly set") + case strings.HasPrefix(arg, "quic://"): + opts.QUIC = true + opts.Request.Server = arg[7:] + opts.Logger.Info("DNS-over-QUIC implicitly set.") + case strings.HasPrefix(arg, "sdns://"): + opts.DNSCrypt = true + opts.Request.Server = arg + opts.Logger.Info("DNSCrypt implicitly set") + default: + opts.Request.Server = arg + } + case strings.Contains(arg, "."): + opts.Logger.Info(arg, "detected as a domain name") + opts.Request.Name, err = idna.ToASCII(arg) + if err != nil { + return err + } + case ok: + opts.Logger.Info(arg, "detected as a type") + // If it's a DNS request, it's a DNS request (obviously) + opts.Request.Type = r + case strings.HasPrefix(arg, "+"): + opts.Logger.Info(arg, "detected as a dig query") + // Dig-style +queries + err = ParseDig(strings.ToLower(arg[1:]), opts) + if err != nil { + return err + } + default: + opts.Logger.Info(arg, "is unknown. Assuming domain") + opts.Request.Name, err = idna.ToASCII(arg) + if err != nil { + return err + } + } + } + + // If nothing was set, set a default + if opts.Request.Name == "" { + opts.Logger.Info("Domain not specified, making a default") + opts.Request.Name = "." + if opts.Request.Type == 0 { + opts.Request.Type = dns.StringToType["NS"] + } + } else { + opts.Logger.Info("Query not specified, making an \"A\" query") + if opts.Request.Type == 0 { + opts.Request.Type = dns.StringToType["A"] + } + } + // + if opts.Request.Server == "" { + opts.Logger.Info("Server not specified, selecting a default") + // Set "defaults" for each if there is no input + switch { + case opts.DNSCrypt: + // This is adguard + opts.Request.Server = "sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20" + case opts.TLS: + opts.Request.Server = "dns.google" + case opts.HTTPS: + opts.Request.Server = "https://dns.cloudflare.com/dns-query" + case opts.QUIC: + opts.Request.Server = "dns.adguard.com" + default: + resolv, err := conf.GetDNSConfig() + if err != nil { + // :^) + opts.Logger.Warn("Could not query system for server. Using default") + opts.Request.Server = "95.216.99.249" + } else { + // Make sure that if IPv4 or IPv6 is asked for it actually uses it + for _, srv := range resolv.Servers { + if opts.IPv4 { + if strings.Contains(srv, ".") { + opts.Request.Server = srv + break + } + } else if opts.IPv6 { + if strings.Contains(srv, ":") { + opts.Request.Server = srv + break + } + } else { + //#nosec -- This isn't used for anything secure + opts.Request.Server = resolv.Servers[rand.Intn(len(resolv.Servers))] + break + } + } + } + } + } + opts.Logger.Info("DNS server set to", opts.Request.Server) + + // Make reverse adresses proper addresses + if opts.Reverse { + opts.Logger.Info("Making reverse DNS query proper *.arpa domain") + if dns.TypeToString[opts.Request.Type] == "A" { + opts.Request.Type = dns.StringToType["PTR"] + } + opts.Request.Name, err = util.ReverseDNS(opts.Request.Name, opts.Request.Type) + if err != nil { + return err + } + } + + // if the domain is not canonical, make it canonical + if !strings.HasSuffix(opts.Request.Name, ".") { + opts.Request.Name = fmt.Sprintf("%s.", opts.Request.Name) + opts.Logger.Debug("Domain made canonical") + } + return nil +} diff --git a/cli/misc_test.go b/cli/misc_test.go new file mode 100644 index 0000000..929c1b4 --- /dev/null +++ b/cli/misc_test.go @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package cli_test + +import ( + "strconv" + "testing" + + "git.froth.zone/sam/awl/cli" + "git.froth.zone/sam/awl/util" + + "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) + opts.Logger = util.InitLogger(0) + 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) + opts.Logger = util.InitLogger(0) + 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) + opts.Logger = util.InitLogger(0) + 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.Logger = util.InitLogger(0) + opts.Reverse = true + err := cli.ParseMiscArgs(args, opts) + assert.NilError(t, err) + assert.Equal(t, opts.Request.Type, dns.StringToType["PTR"]) +} + +func TestParseInvalidPTR(t *testing.T) { + t.Parallel() + args := []string{"8.88.8"} + opts := new(cli.Options) + opts.Logger = util.InitLogger(0) + opts.Reverse = true + err := cli.ParseMiscArgs(args, opts) + assert.ErrorContains(t, err, "unrecognized address") +} + +func TestDefaultServer(t *testing.T) { + t.Parallel() + tests := []struct { + in string + want string + }{ + {"DNSCRYPT", "sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20"}, + {"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) + opts.Logger = util.InitLogger(0) + switch test.in { + case "DNSCRYPT": + opts.DNSCrypt = true + 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 TestFlagSetting(t *testing.T) { + t.Parallel() + tests := []struct { + in []string + }{ + {[]string{"@sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20"}}, + {[]string{"@tls://dns.google"}}, + {[]string{"@https://dns.cloudflare.com/dns-query"}}, + {[]string{"@quic://dns.adguard.com"}}, + } + for i, test := range tests { + test := test + i := i + t.Run(strconv.Itoa(i), func(t *testing.T) { + opts := new(cli.Options) + opts.Logger = util.InitLogger(0) + t.Parallel() + err := cli.ParseMiscArgs(test.in, opts) + assert.NilError(t, err) + switch i { + case 0: + assert.Assert(t, opts.DNSCrypt) + case 1: + assert.Assert(t, opts.TLS) + case 2: + assert.Assert(t, opts.HTTPS) + case 3: + assert.Assert(t, opts.QUIC) + + } + }) + } +} + +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) + opts.Logger = util.InitLogger(0) + //nolint:errcheck // Only make sure the program does not crash + cli.ParseMiscArgs(args, opts) + }) +} diff --git a/cli/options.go b/cli/options.go new file mode 100644 index 0000000..16d0978 --- /dev/null +++ b/cli/options.go @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package cli + +import ( + "errors" + + "git.froth.zone/sam/awl/internal/helpers" + "git.froth.zone/sam/awl/logawl" +) + +// CLI options structure. +type Options struct { + Logger *logawl.Logger // Logger + Port int // DNS port + IPv4 bool // Force IPv4 + IPv6 bool // Force IPv6 + DNSSEC bool // Enable DNSSEC + TCP bool // Query with TCP + DNSCrypt bool // Query over DNSCrypt + TLS bool // Query over TLS + HTTPS bool // Query over HTTPS + QUIC bool // Query over QUIC + Truncate bool // Ignore truncation + AA bool // Set Authoratative Answer + AD bool // Set Authenticated Data + CD bool // Set CD + QR bool // Set QueRy + RD bool // Set Recursion Desired + RA bool // Set Recursion Available + TC bool // Set TC (TrunCated) + 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 + + Display Displays // Display options + Request helpers.Request // DNS reuqest +} + +// What to (and not to) display +type Displays struct { + // Comments bool + Question bool // QUESTION SECTION + Answer bool // ANSWER SECTION + Authority bool // AUTHORITY SECTION + Additional bool // ADDITIONAL SECTION + Statistics bool // Query time, message size, etc. +} + +var ErrNotError = errors.New("not an error") diff --git a/cli_test.go b/cli_test.go deleted file mode 100644 index ca1fc89..0000000 --- a/cli_test.go +++ /dev/null @@ -1,126 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause - -package main - -import ( - "os" - "testing" - - "git.froth.zone/sam/awl/query" - "github.com/miekg/dns" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestApp(t *testing.T) { - app := prepareCLI() - // What more can even be done lmao - require.NotNil(t, app) -} - -func TestArgParse(t *testing.T) { - tests := []struct { - in []string - want query.Answers - }{ - { - []string{"@::1", "localhost", "AAAA"}, - query.Answers{Server: "::1", Request: dns.TypeAAAA, Name: "localhost"}, - }, - { - []string{"@1.0.0.1", "google.com"}, - query.Answers{Server: "1.0.0.1", Request: dns.TypeA, Name: "google.com"}, - }, - { - []string{"@8.8.4.4"}, - query.Answers{Server: "8.8.4.4", Request: dns.TypeNS, Name: "."}, - }, - } - for _, test := range tests { - act, err := parseArgs(test.in, query.Options{}) - assert.Nil(t, err) - assert.Equal(t, test.want, act) - } -} - -func TestQuery(t *testing.T) { - app := prepareCLI() - args := os.Args[0:1] - args = append(args, "--Treebug") - err := app.Run(args) - assert.NotNil(t, err) -} - -func TestNoArgs(t *testing.T) { - app := prepareCLI() - args := os.Args[0:1] - args = append(args, "--no-truncate") - err := app.Run(args) - assert.Nil(t, err) -} - -func TestFlags(t *testing.T) { - app := prepareCLI() - args := os.Args[0:1] - args = append(args, "--debug") - args = append(args, "--short") - args = append(args, "-4") - err := app.Run(args) - assert.Nil(t, err) -} - -func TestHTTPS(t *testing.T) { - app := prepareCLI() - args := os.Args[0:1] - args = append(args, "-H") - args = append(args, "@https://cloudflare-dns.com/dns-query") - args = append(args, "git.froth.zone") - err := app.Run(args) - assert.Nil(t, err) -} - -func TestJSON(t *testing.T) { - app := prepareCLI() - args := os.Args[0:1] - args = append(args, "-j") - args = append(args, "git.froth.zone") - err := app.Run(args) - assert.Nil(t, err) -} - -func TestQUIC(t *testing.T) { - app := prepareCLI() - args := os.Args[0:1] - args = append(args, "-Q") - args = append(args, "@dns.adguard.com") - args = append(args, "git.froth.zone") - err := app.Run(args) - assert.Nil(t, err) -} - -func TestReverse(t *testing.T) { - app := prepareCLI() - args := os.Args[0:1] - args = append(args, "-x") - args = append(args, "8.8.8.8") - err := app.Run(args) - assert.Nil(t, err) -} - -func FuzzCli(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) { - app := prepareCLI() - args := os.Args[0:1] - args = append(args, orig) - err := app.Run(args) - if err != nil { - assert.ErrorContains(t, err, "domain must be fully qualified") - } - assert.Nil(t, err) - }) -} 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 fa81fd2..af8379a 100644 --- a/conf/plan9_test.go +++ b/conf/plan9_test.go @@ -1,14 +1,17 @@ // 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) { + t.Parallel() ndbs := []struct { in string want string @@ -27,9 +30,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 @@ -38,8 +46,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/coverage/.gitkeep b/coverage/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/doc/awl.1 b/doc/awl.1 new file mode 100644 index 0000000..3145c0f --- /dev/null +++ b/doc/awl.1 @@ -0,0 +1,244 @@ +.\" Generated by scdoc 1.11.2 +.\" Complete documentation for this program is not available as a GNU info page +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.nh +.ad l +.\" Begin generated content: +.TH "awl" "1" "2022-07-25" +.PP +.SH NAME +awl - DNS lookup tool +.PP +.SH SYNOPSIS +\fBawl\fR [ \fIOPTIONS\fR ] \fIname\fR [ \fI@server\fR ] [ \fItype\fR ] +.br +where +.PP +\fIname\fR is the query to make (\fBexample: froth.\&zone\fR) +.br +\fI@server\fR is the server to query (\fBexample: dns.\&froth.\&zone\fR) +.br +\fItype\fR is the DNS resource type (\fBexample: AAAA\fR) +.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 leatherworking.\& +.PP +\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 +.SH OPTIONS +.RS 4 +Dig-like +[no]flags 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-v\fR \fIvalue\fR +.br + Set verbosity (currently WIP) +.PP +\fB-V\fR +.br + Print the version and exit.\& +.PP +\fB-h\fR +.br + Show a "short" help message.\& +.PP +.RE +.SS Query Options +.RS 4 +\fB-4\fR +.br + Only make query over IPv4 +.PP +\fB-6\fR +.br + Only make query over IPv6 +.PP +\fB-p\fR, \fB--port\fR \fIport\fR +.br + Sets the port to query.\& +.br + +.br +\fIDefault Ports\fR: +.RS 4 +.PD 0 +.IP \(bu 4 +\fI53\fR for \fBUDP\fR and \fBTCP\fR +.IP \(bu 4 +\fI853\fR for \fBTLS\fR and \fBQUIC\fR +.IP \(bu 4 +\fI443\fR for \fBHTTPS\fR +.PD +.PP +.RE +\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--tcp\fR, \fB+tcp\fR, \fB+vc\fR +.br + Use TCP for the query (see \fIRFC 7766\fR) +.PP +\fB--dnscrypt\fR, \fB+dnscrypt\fR +.br + Use DNSCrypt +.PP +\fB-T\fR, \fB--tls\fR, \fB+tls\fR +.br + Use DNS-over-TLS, implies \fB--tcp\fR (see \fIRFC 7858\fR) +.PP +\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+quic\fR +.br + Use DNS-over-QUIC (see \fIRFC 9250\fR) +.PP +\fB-x\fR, \fB--reverse\fR +.br + Do a reverse lookup.\& Sets default \fItype\fR to PTR.\& +.br + \fBawl\fR automatically makes an IP or phone number canonical.\& +.PP +\fB--timeout\fR \fIseconds\fR, \fB+timeout=\fR\fIseconds\fR +.br + Set the timeout period.\& Floating point numbers are accepted.\& +.br + 0.\&5 seconds is the minimum.\& +.PP +\fB--retries\fR \fIint\fR, \fB+tries\fR=\fIint\fR, \fB+ retry\fR=\fIint\fR +.br + Set the number of retries.\& +.br + Retry is one more than tries, dig style +.PP +.RE +.SS DNS Flags +.PP +.RS 4 +\fB--aa=[false]\fR, \fB+[no]aaflag\fR +.br + (Set, Unset) AA (Authoritative Answer) flag +.PP +\fB--ad=[false]\fR, \fB+[no]adflag\fR +.br + (Set, Unset) AD (Authenticated Data) flag +.PP +\fB--tc=[false]\fR, \fB+[no]tcflag\fR +.br + (Set, Unset) TC (TrunCated) flag +.PP +\fB-z=[false]\fR, \fB+[no]zflag\fR +.br + (Set, Unset) Z (Zero) flag +.PP +\fB--cd=[false]\fR, \fB+[no]cdflag\fR +.br + (Set, Unset) CD (Checking Disabled) flag +.PP +\fB--qr=[false]\fR, \fB+[no]qrflag\fR +.br + (Set, Unset) QR (QueRy) flag +.PP +\fB--rd=[true]\fR, \fB+[no]rdflag\fR +.br + (Set, Unset) RD (Recursion Desired) flag +.PP +\fB--ra=[false]\fR, \fB+[no]raflag\fR +.br + (Set, Unset) RA (Recursion Available) flag +.PP +.RE +.SS Output Display +.RS 4 +\fB--no-question\fR, \fB+noquestion\fR +.br + Do not display the Question section +.PP +\fB--no-answer\fR, \fB+noanswer\fR +.br + Do not display the Answer section +.PP +\fB--no-answer\fR, \fB+noanswer\fR +.br + Do not display the Answer section +.PP +\fB--no-authority\fR, \fB+noauthority\fR +.br + Do not display the Authority section +.PP +\fB--no-additional\fR, \fB+noadditional\fR +.br + Do not display the Additional section +.PP +\fB--no-statistics\fR, \fB+nostats\fR +.br + Do not display the Statistics (additional comments) section +.PP +.RE +.SS Output Formats +.RS 4 +\fB-j\fR, \fB--json\fR, \fB+json\fR +.br + Print the query results as JSON.\& +.PP +\fB-X\fR, \fB--xml\fR, \fB+xml\fR +.br + Print the query results as XML.\& +.PP +\fB-y\fR, \fB--yaml\fR, \fB+yaml\fR +.br + Print the query results as YAML.\& +.PP +\fB-s\fR, \fB--short\fR, \fB+short\fR +.br + Print just the address of the answer.\& +.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/wiki b/doc/wiki new file mode 160000 index 0000000..0fba1fb --- /dev/null +++ b/doc/wiki @@ -0,0 +1 @@ +Subproject commit 0fba1fbe4b12e8c88514b3f7d98be3e75a5a034d diff --git a/go.mod b/go.mod index fbb831f..3a8063a 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,26 @@ module git.froth.zone/sam/awl go 1.18 require ( + github.com/ameshkov/dnscrypt/v2 v2.2.3 github.com/lucas-clemente/quic-go v0.28.1 github.com/miekg/dns v1.1.50 - github.com/urfave/cli/v2 v2.11.1 + github.com/stefansundin/go-zflag v1.1.1 golang.org/x/net v0.0.0-20220725212005-46097bf591d3 + 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/AdguardTeam/golibs v0.10.9 // indirect + github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect + github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect + github.com/ameshkov/dnsstamps v1.0.3 // 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 ( github.com/cheekybits/genny v1.0.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect @@ -26,10 +30,8 @@ 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/russross/blackfriday/v2 v2.1.0 // indirect - github.com/stretchr/testify v1.8.0 - github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect + 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-20220722155257-8c9f86f7a55f golang.org/x/text v0.3.7 // indirect diff --git a/go.sum b/go.sum index 9bb7954..bcdf8cd 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,19 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= +github.com/AdguardTeam/golibs v0.10.9 h1:F9oP2da0dQ9RQDM1lGR7LxUTfUWu8hEFOs4icwAkKM0= +github.com/AdguardTeam/golibs v0.10.9/go.mod h1:W+5rznZa1cSNSFt+gPS7f4Wytnr9fOrd5ZYqwadPw14= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= +github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw= +github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us= +github.com/ameshkov/dnscrypt/v2 v2.2.3 h1:X9UP5AHtwp46Ji+sGFfF/1Is6OPI/SjxLqhKpx0P5UI= +github.com/ameshkov/dnscrypt/v2 v2.2.3/go.mod h1:xJB9cE1/GF+NB6EEQqRlkoa4bjcV2w7VYn1G+zVq7Bs= +github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= +github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo= +github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= @@ -17,8 +28,6 @@ github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitf github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -58,6 +67,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= @@ -70,6 +80,7 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:Fecb github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -79,10 +90,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lucas-clemente/quic-go v0.27.2 h1:zsMwwniyybb8B/UDNXRSYee7WpQJVOcjQEGgpw2ikXs= -github.com/lucas-clemente/quic-go v0.27.2/go.mod h1:vXgO/11FBSKM+js1NxoaQ/bPtVFYfB7uxhfHXyMhl1A= -github.com/lucas-clemente/quic-go v0.28.0 h1:9eXVRgIkMQQyiyorz/dAaOYIx3TFzXsIFkNFz4cxuJM= -github.com/lucas-clemente/quic-go v0.28.0/go.mod h1:oGz5DKK41cJt5+773+BSO9BXDsREY4HLf7+0odGAPO0= github.com/lucas-clemente/quic-go v0.28.1 h1:Uo0lvVxWg5la9gflIF9lwa39ONq85Xq2D91YNEIslzU= github.com/lucas-clemente/quic-go v0.28.1/go.mod h1:oGz5DKK41cJt5+773+BSO9BXDsREY4HLf7+0odGAPO0= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= @@ -98,6 +105,7 @@ github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 h1:7m/WlWcSROrcK5NxuXaxYD32B github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -126,10 +134,7 @@ github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= @@ -155,27 +160,22 @@ 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= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= -github.com/urfave/cli/v2 v2.10.3 h1:oi571Fxz5aHugfBAJd5nkwSk3fzATXtMlpxdLylSCMo= -github.com/urfave/cli/v2 v2.10.3/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= -github.com/urfave/cli/v2 v2.11.0 h1:c6bD90aLd2iEsokxhxkY5Er0zA2V9fId2aJfwmrF+do= -github.com/urfave/cli/v2 v2.11.0/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= -github.com/urfave/cli/v2 v2.11.1 h1:UKK6SP7fV3eKOefbS87iT9YHefv7iB/53ih6e+GNAsE= -github.com/urfave/cli/v2 v2.11.1/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= @@ -184,14 +184,15 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= @@ -207,23 +208,15 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 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-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw= -golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220706163947-c90051bbdb60 h1:8NSylCMxLW4JvserAndSgFL7aPli6A68yf0bYFTcWCM= -golang.org/x/net v0.0.0-20220706163947-c90051bbdb60/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6flUMtzCwzlvkxEQtHHB0= -golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220725212005-46097bf591d3 h1:2yWTtPWWRcISTw3/o+s/Y4UOMnQL71DWyToOANFusCg= @@ -248,36 +241,24 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b h1:2n253B2r0pYSmEV+UNCQoPfU/FiaizQEK5Gu4Bq4JE8= -golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220701225701-179beb0bd1a1 h1:+Lm8wRwJpsVpTHuM4tHTwgxjPzv/bjxsHt2cW5EY7XU= -golang.org/x/sys v0.0.0-20220701225701-179beb0bd1a1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220702020025-31831981b65f h1:xdsejrW/0Wf2diT5CPp3XmKUNbr7Xvw8kYilQ+6qjRY= -golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e h1:CsOuNlbOuf0mzxJIefr6Q4uAUetRUwZE4qt7VfzP+xo= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d h1:/m5NbqQelATgoSPVC2Z23sR4kVNokFwDDyWh/3rGY+I= -golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e h1:NHvCuwuS43lGnYhten69ZWqi2QOj/CiDNcKbVqwVoew= -golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -298,7 +279,9 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 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-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 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= @@ -306,6 +289,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= @@ -342,12 +326,15 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.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..ddc0943 --- /dev/null +++ b/internal/helpers/query.go @@ -0,0 +1,25 @@ +// 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 + Timeout time.Duration // The maximum timeout + Retries int // Number of queries to retry +} 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..70c9416 100644 --- a/logawl/logawl.go +++ b/logawl/logawl.go @@ -9,44 +9,46 @@ 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: - return "FATAL ", nil - case 1: return "ERROR ", nil + case 1: + return "WARN ", nil case 2: return "INFO ", nil case 3: return "DEBUG ", nil } - return "", fmt.Errorf("invalid log level choice") + return "", fmt.Errorf("invalid log level") } func (l *Logger) IsLevel(level Level) bool { @@ -54,20 +56,20 @@ func (l *Logger) IsLevel(level Level) bool { } var AllLevels = []Level{ - FatalLevel, - ErrorLevel, + ErrLevel, + WarnLevel, InfoLevel, DebugLevel, } const ( - // Fatal logs (will call exit(1)) - FatalLevel Level = iota + // Fatal logs (will call exit(1)). + ErrLevel Level = iota - // Error logs - ErrorLevel + // Error logs. + WarnLevel - // 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 607f516..b703ba2 100644 --- a/logawl/logger.go +++ b/logawl/logger.go @@ -28,15 +28,25 @@ func (l *Logger) Println(level Level, v ...any) { if l.IsLevel(level) { switch level { //Goes through log levels and does stuff based on them (Fatal os.Exit...etc) case 0: - l.Printer(0, fmt.Sprintln(v...)) //Fatal level - os.Exit(1) + err := l.Printer(0, fmt.Sprintln(v...)) //Fatal level + if err != nil { + fmt.Fprintln(os.Stderr, "FATAL: Logger failed: ", err) + } case 1: - l.Printer(1, fmt.Sprintln(v...)) //Error level - os.Exit(2) + err := l.Printer(1, fmt.Sprintln(v...)) //Error level + if err != nil { + fmt.Fprintln(os.Stderr, "FATAL: Logger failed: ", err) + } case 2: - 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) + } case 3: - 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) + } default: break } @@ -44,7 +54,7 @@ func (l *Logger) Println(level Level, v ...any) { } // 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 { +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 // maybe there can be an append func or something in the future @@ -80,12 +90,15 @@ func (l *Logger) Printer(level Level, s string) error { defer l.Mu.Unlock() l.buf = l.buf[:0] - l.formatHeader(&l.buf, now, line, level) + err := l.FormatHeader(&l.buf, now, line, level) + if err != nil { + return err + } l.buf = append(l.buf, s...) if len(s) == 0 || s[len(s)-1] != '\n' { l.buf = append(l.buf, '\n') } - _, err := l.Out.Write(l.buf) + _, err = l.Out.Write(l.buf) return err } @@ -120,12 +133,12 @@ func (l *Logger) Info(v ...any) { l.Println(InfoLevel, v...) } -// Call print directly with Error level -func (l *Logger) Error(v ...any) { - l.Println(ErrorLevel, v...) +// Call print directly with Warn level +func (l *Logger) Warn(v ...any) { + l.Println(WarnLevel, v...) } -// Call print directly with Fatal level -func (l *Logger) Fatal(v ...any) { - l.Println(FatalLevel, v...) +// 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 606cd5c..ffbcf60 100644 --- a/logawl/logging_test.go +++ b/logawl/logging_test.go @@ -1,52 +1,53 @@ -package logawl +// SPDX-License-Identifier: BSD-3-Clause + +package logawl_test import ( "bytes" "testing" "time" - "github.com/stretchr/testify/assert" + "git.froth.zone/sam/awl/logawl" + + "gotest.tools/v3/assert" ) -var logger = New() +var logger = logawl.New() func TestLogawl(t *testing.T) { + t.Parallel() - assert.Equal(t, Level(2), logger.Level) //cast 2 (int) to 2 (level) - - //Validate setting and getting levels from memory works - for i := range AllLevels { - logger.SetLevel(Level(i)) - assert.Equal(t, Level(i), logger.GetLevel()) + 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 AllLevels { - m[i], err = logger.UnMarshalLevel(Level(i)) - assert.Nil(t, err) + + for i := range logawl.AllLevels { + m[i], err = logger.UnMarshalLevel(logawl.Level(i)) + assert.NilError(t, err) } - //iterate over map and assert equal - for i := range AllLevels { - lv, err := logger.UnMarshalLevel(Level(i)) - assert.Nil(t, err) + for i := range logawl.AllLevels { + lv, err := logger.UnMarshalLevel(logawl.Level(i)) + assert.NilError(t, err) assert.Equal(t, m[i], lv) } - lv, err := logger.UnMarshalLevel(Level(9001)) - assert.NotNil(t, err) + lv, err := logger.UnMarshalLevel(logawl.Level(9001)) assert.Equal(t, "", lv) - assert.ErrorContains(t, err, "invalid log level choice") + assert.ErrorContains(t, err, "invalid log level") } func TestLogger(t *testing.T) { + t.Parallel() - for i := range AllLevels { + for i := range logawl.AllLevels { // only test non-exiting log levels switch i { case 1: @@ -72,12 +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, 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 new file mode 100644 index 0000000..68587f0 --- /dev/null +++ b/main.go @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package main + +import ( + "encoding/json" + "encoding/xml" + "errors" + "fmt" + "os" + "strconv" + "strings" + "time" + + "git.froth.zone/sam/awl/cli" + "git.froth.zone/sam/awl/internal/helpers" + "git.froth.zone/sam/awl/query" + + "gopkg.in/yaml.v2" +) + +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.Error(err) + os.Exit(1) + } + var resp helpers.Response + + // Retry queries if a query fails + for i := 0; i < opts.Request.Retries; i++ { + resp, err = query.CreateQuery(opts) + if err == nil { + break + } else { + opts.Logger.Warn("Retrying request, error", err) + } + } + + // Query failed, make it fail + if err != nil { + opts.Logger.Error(err) + os.Exit(9) + } + switch { + case opts.JSON: + opts.Logger.Info("Printing as JSON") + json, err := json.MarshalIndent(resp.DNS, "", " ") + if err != nil { + opts.Logger.Error(err) + os.Exit(10) + } + fmt.Println(string(json)) + case opts.XML: + opts.Logger.Info("Printing as XML") + xml, err := xml.MarshalIndent(resp.DNS, "", " ") + if err != nil { + opts.Logger.Error(err) + os.Exit(10) + } + fmt.Println(string(xml)) + case opts.YAML: + opts.Logger.Info("Printing as YAML") + yaml, err := yaml.Marshal(resp.DNS) + if err != nil { + opts.Logger.Error(err) + os.Exit(10) + } + fmt.Println(string(yaml)) + default: + if !opts.Short { + // Print everything + + if !opts.Display.Question { + resp.DNS.Question = nil + opts.Logger.Info("Disabled question display") + } + if !opts.Display.Answer { + resp.DNS.Answer = nil + opts.Logger.Info("Disabled answer display") + } + if !opts.Display.Authority { + resp.DNS.Ns = nil + opts.Logger.Info("Disabled authority display") + } + if !opts.Display.Additional { + resp.DNS.Extra = nil + opts.Logger.Info("Disabled additional display") + } + + fmt.Println(resp.DNS) + + if opts.Display.Statistics { + fmt.Println(";; Query time:", resp.RTT) + + // Add extra information to server string + var extra string + switch { + case opts.TCP: + extra = ":" + strconv.Itoa(opts.Port) + " (TCP)" + case opts.TLS: + extra = ":" + strconv.Itoa(opts.Port) + " (TLS)" + case opts.HTTPS, opts.DNSCrypt: + extra = "" + case opts.QUIC: + extra = ":" + strconv.Itoa(opts.Port) + " (QUIC)" + default: + extra = ":" + strconv.Itoa(opts.Port) + " (UDP)" + } + + fmt.Println(";; SERVER:", opts.Request.Server+extra) + fmt.Println(";; WHEN:", time.Now().Format(time.RFC1123Z)) + fmt.Println(";; MSG SIZE rcvd:", resp.DNS.Len()) + } + + } else { + // Print just the responses, nothing else + for _, res := range resp.DNS.Answer { + temp := strings.Split(res.String(), "\t") + fmt.Println(temp[len(temp)-1]) + } + } + } +} diff --git a/mkfile b/mkfile new file mode 100644 index 0000000..053cea2 --- /dev/null +++ b/mkfile @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Plan 9 mkfile + +GO = go +PROG = awl +LDFLAGS = '-s -w' +GOFLAGS = -ldflags=$LDFLAGS + +CGO_ENABLED = 0 + +$PROG: + $GO build $GOFLAGS -o $PROG '-buildvcs=false' . + +install: $PROG + $GO install $GOFLAGS . + cp doc/$PROG.1 /sys/man/1/$PROG + +test: + $GO test -v -cover -coverprofile=coverage/coverage.out ./... + +fmt: + gofmt -w -s . + +vet: + $GO vet ./... + +lint: fmt vet + +clean: + $GO clean diff --git a/query.go b/query.go deleted file mode 100644 index e90f933..0000000 --- a/query.go +++ /dev/null @@ -1,199 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause - -package main - -import ( - "encoding/json" - "fmt" - "net" - "strconv" - "strings" - "time" - - "git.froth.zone/sam/awl/query" - "git.froth.zone/sam/awl/util" - "github.com/miekg/dns" - "github.com/urfave/cli/v2" -) - -func doQuery(c *cli.Context) error { - var ( - err error - ) - // load cli flags into options struct - Options := query.Options{ - Logger: util.InitLogger(c.Bool("debug")), - Port: c.Int("port"), - IPv4: c.Bool("4"), - IPv6: c.Bool("6"), - DNSSEC: c.Bool("dnssec"), - Short: c.Bool("short"), - TCP: c.Bool("tcp"), - TLS: c.Bool("tls"), - HTTPS: c.Bool("https"), - QUIC: c.Bool("quic"), - Truncate: c.Bool("no-truncate"), - AA: c.Bool("aa"), - TC: c.Bool("tc"), - Z: c.Bool("z"), - CD: c.Bool("cd"), - NoRD: c.Bool("no-rd"), - NoRA: c.Bool("no-ra"), - Reverse: c.Bool("reverse"), - Debug: c.Bool("debug"), - } - Options.Answers, err = parseArgs(c.Args().Slice(), Options) - if err != nil { - Options.Logger.Error("Unable to parse args") - return err - } - msg := new(dns.Msg) - - if Options.Reverse { - if dns.TypeToString[Options.Answers.Request] == "A" { - Options.Answers.Request = dns.StringToType["PTR"] - } - Options.Answers.Name, err = util.ReverseDNS(Options.Answers.Name, Options.Answers.Request) - if err != nil { - return err - } - } - - // if the domain is not canonical, make it canonical - if !strings.HasSuffix(Options.Answers.Name, ".") { - Options.Answers.Name = fmt.Sprintf("%s.", Options.Answers.Name) - } - msg.SetQuestion(Options.Answers.Name, Options.Answers.Request) - // If port is not set, set it - if Options.Port == 0 { - if Options.TLS || Options.QUIC { - Options.Port = 853 - } else { - Options.Port = 53 - } - } - Options.Logger.Debug("setting any message flags") - // Make this authoritative (does this do anything?) - if Options.AA { - Options.Logger.Debug("making message authorative") - msg.Authoritative = true - } - // Set truncated flag (why) - if Options.TC { - msg.Truncated = true - } - // Set the zero flag if requested (does nothing) - if Options.Z { - Options.Logger.Debug("setting to zero") - msg.Zero = true - } - // Disable DNSSEC validation - if Options.CD { - Options.Logger.Debug("disabling DNSSEC validation") - msg.CheckingDisabled = true - } - // Disable wanting recursion - if Options.NoRD { - Options.Logger.Debug("disabling recursion") - msg.RecursionDesired = false - } - // Disable recursion being available (I don't think this does anything) - if Options.NoRA { - msg.RecursionAvailable = false - } - // Set DNSSEC if requested - if Options.DNSSEC { - Options.Logger.Debug("using DNSSEC") - msg.SetEdns0(1232, true) - } - - resolver, err := query.LoadResolver(Options.Answers.Server, Options) - if err != nil { - return err - } - - if Options.Debug { - Options.Logger.SetLevel(3) - } - - Options.Logger.Debug("Starting awl") - - var in = Options.Answers.DNS - - // Make the DNS request - if Options.HTTPS { - in, Options.Answers.RTT, err = resolver.LookUp(msg) - } else if Options.QUIC { - in, Options.Answers.RTT, err = resolver.LookUp(msg) - } else { - Options.Answers.Server = net.JoinHostPort(Options.Answers.Server, strconv.Itoa(Options.Port)) - d := new(dns.Client) - - // Set TCP/UDP, depending on flags - if Options.TCP || Options.TLS { - d.Net = "tcp" - } else { - Options.Logger.Debug("using udp") - d.Net = "udp" - } - - // Set IPv4 or IPv6, depending on flags - switch { - case Options.IPv4: - d.Net += "4" - case Options.IPv6: - d.Net += "6" - } - - // Add TLS, if requested - if Options.TLS { - d.Net += "-tls" - } - - in, Options.Answers.RTT, err = d.Exchange(msg, Options.Answers.Server) - if err != nil { - return err - } - // If UDP truncates, use TCP instead (unless truncation is to be ignored) - if in.MsgHdr.Truncated && !Options.Truncate { - fmt.Printf(";; Truncated, retrying with TCP\n\n") - d.Net = "tcp" - switch { - case Options.IPv4: - d.Net += "4" - case Options.IPv4: - d.Net += "6" - } - in, Options.Answers.RTT, err = d.Exchange(msg, Options.Answers.Server) - } - } - - if err != nil { - return err - } - - if c.Bool("json") { - json, err := json.MarshalIndent(in, "", " ") - if err != nil { - return err - } - fmt.Println(string(json)) - } else { - if !c.Bool("short") { - // Print everything - fmt.Println(in) - fmt.Println(";; Query time:", Options.Answers.RTT) - fmt.Println(";; SERVER:", Options.Answers.Server) - fmt.Println(";; WHEN:", time.Now().Format(time.RFC1123Z)) - fmt.Println(";; MSG SIZE rcvd:", in.Len()) - } else { - // Print just the responses, nothing else - for _, res := range in.Answer { - temp := strings.Split(res.String(), "\t") - fmt.Println(temp[len(temp)-1]) - } - } - } - - return nil -} diff --git a/query/DNSCrypt.go b/query/DNSCrypt.go new file mode 100644 index 0000000..12751f8 --- /dev/null +++ b/query/DNSCrypt.go @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package query + +import ( + "time" + + "git.froth.zone/sam/awl/cli" + "git.froth.zone/sam/awl/internal/helpers" + + "github.com/ameshkov/dnscrypt/v2" + "github.com/miekg/dns" +) + +type DNSCryptResolver struct { + opts cli.Options +} + +func (r *DNSCryptResolver) LookUp(msg *dns.Msg) (helpers.Response, error) { + + client := dnscrypt.Client{ + Timeout: r.opts.Request.Timeout, + UDPSize: 1232, + } + + if r.opts.TCP || r.opts.TLS { + client.Net = "tcp" + } else { + client.Net = "udp" + } + + switch { + case r.opts.IPv4: + client.Net += "4" + case r.opts.IPv6: + client.Net += "6" + } + r.opts.Logger.Debug("Using", client.Net, "for making the request") + + resolverInf, err := client.Dial(r.opts.Request.Server) + if err != nil { + return helpers.Response{}, err + } + + now := time.Now() + res, err := client.Exchange(msg, resolverInf) + rtt := time.Since(now) + + if err != nil { + return helpers.Response{}, err + } + r.opts.Logger.Info("Request successful") + + return helpers.Response{ + DNS: res, + RTT: rtt, + }, nil +} diff --git a/query/DNSCrypt_test.go b/query/DNSCrypt_test.go new file mode 100644 index 0000000..126a073 --- /dev/null +++ b/query/DNSCrypt_test.go @@ -0,0 +1,52 @@ +// 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 TestDNSCrypt(t *testing.T) { + tests := []struct { + opt cli.Options + }{ + { + cli.Options{ + Logger: util.InitLogger(0), + DNSCrypt: true, + Request: helpers.Request{ + Server: "sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", + Type: dns.TypeA, + Name: "example.com.", + }, + }, + }, + { + cli.Options{ + Logger: util.InitLogger(0), + DNSCrypt: true, + TCP: true, + IPv4: true, + Request: helpers.Request{ + Server: "sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_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{}) + } + +} diff --git a/query/HTTPS.go b/query/HTTPS.go index f72470c..b15dd81 100644 --- a/query/HTTPS.go +++ b/query/HTTPS.go @@ -9,53 +9,56 @@ import ( "net/http" "time" + "git.froth.zone/sam/awl/cli" + "git.froth.zone/sam/awl/internal/helpers" "github.com/miekg/dns" ) type HTTPSResolver struct { - server string - opts Options + opts cli.Options } -func (r *HTTPSResolver) LookUp(msg *dns.Msg) (*dns.Msg, time.Duration, error) { - var resp Response - httpR := &http.Client{} +func (r *HTTPSResolver) LookUp(msg *dns.Msg) (helpers.Response, error) { + var resp helpers.Response + httpR := &http.Client{ + Timeout: r.opts.Request.Timeout, + } buf, err := msg.Pack() if err != nil { - return nil, 0, 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)) + req, err := http.NewRequest("POST", r.opts.Request.Server, bytes.NewBuffer(buf)) if err != nil { - return nil, 0, fmt.Errorf("DoH: %s", err.Error()) + return helpers.Response{}, fmt.Errorf("DoH: %w", err) } req.Header.Set("Content-Type", "application/dns-message") req.Header.Set("Accept", "application/dns-message") now := time.Now() res, err := httpR.Do(req) - resp.Answers.RTT = time.Since(now) + resp.RTT = time.Since(now) if err != nil { - return nil, 0, fmt.Errorf("DoH HTTP request error: %s", err.Error()) + return helpers.Response{}, fmt.Errorf("DoH HTTP request error: %w", err) } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return nil, 0, 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 nil, 0, fmt.Errorf("DoH body read error: %s", err.Error()) + return helpers.Response{}, fmt.Errorf("DoH body read error: %w", err) } - resp.DNS = dns.Msg{} + resp.DNS = &dns.Msg{} r.opts.Logger.Debug("unpacking response") err = resp.DNS.Unpack(fullRes) if err != nil { - return nil, 0, fmt.Errorf("DoH dns message unpack error: %s", err.Error()) + return helpers.Response{}, fmt.Errorf("DoH dns message unpack error: %w", err) } - return &resp.DNS, resp.Answers.RTT, nil + return resp, nil } diff --git a/query/HTTPS_test.go b/query/HTTPS_test.go new file mode 100644 index 0000000..b6efae6 --- /dev/null +++ b/query/HTTPS_test.go @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package query_test + +import ( + "fmt" + "strings" + "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 TestResolveHTTPS(t *testing.T) { + t.Parallel() + var err error + opts := cli.Options{ + HTTPS: true, + Logger: util.InitLogger(0), + Request: helpers.Request{ + Server: "https://dns9.quad9.net/dns-query", + Type: dns.TypeA, + Name: "git.froth.zone.", + }, + } + // testCase := helpers.Request{Server: "https://dns9.quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone."} + resolver, err := query.LoadResolver(opts) + assert.NilError(t, err) + + msg := new(dns.Msg) + msg.SetQuestion(opts.Request.Name, opts.Request.Type) + // msg = msg.SetQuestion(testCase.Name, testCase.Type) + res, err := resolver.LookUp(msg) + assert.NilError(t, err) + assert.Assert(t, res != helpers.Response{}) +} + +func Test2ResolveHTTPS(t *testing.T) { + t.Parallel() + opts := cli.Options{ + HTTPS: true, + Logger: util.InitLogger(0), + } + var err error + testCase := helpers.Request{Server: "dns9.quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone"} + resolver, err := query.LoadResolver(opts) + assert.NilError(t, err) + msg := new(dns.Msg) + msg.SetQuestion(testCase.Name, testCase.Type) + // msg = msg.SetQuestion(testCase.Name, testCase.Type) + res, err := resolver.LookUp(msg) + assert.ErrorContains(t, err, "fully qualified") + assert.Equal(t, res, helpers.Response{}) +} + +func Test3ResolveHTTPS(t *testing.T) { + t.Parallel() + opts := cli.Options{ + HTTPS: true, + Logger: util.InitLogger(0), + } + var err error + testCase := helpers.Request{Server: "dns9..quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone."} + // if the domain is not canonical, make it canonical + if !strings.HasSuffix(testCase.Name, ".") { + testCase.Name = fmt.Sprintf("%s.", testCase.Name) + } + resolver, err := query.LoadResolver(opts) + assert.NilError(t, err) + msg := new(dns.Msg) + msg.SetQuestion(testCase.Name, testCase.Type) + // msg = msg.SetQuestion(testCase.Name, testCase.Type) + res, err := resolver.LookUp(msg) + assert.ErrorContains(t, err, "request error") + assert.Equal(t, res, helpers.Response{}) +} + +func Test404ResolveHTTPS(t *testing.T) { + t.Parallel() + var err error + opts := cli.Options{ + HTTPS: true, + Logger: util.InitLogger(0), + Request: helpers.Request{ + Server: "https://dns9.quad9.net/dns", + Type: dns.TypeA, + Name: "git.froth.zone.", + }, + } + // testCase := helpers.Request{Server: "https://dns9.quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone."} + resolver, err := query.LoadResolver(opts) + assert.NilError(t, err) + + msg := new(dns.Msg) + msg.SetQuestion(opts.Request.Name, opts.Request.Type) + // msg = msg.SetQuestion(testCase.Name, testCase.Type) + res, err := resolver.LookUp(msg) + assert.ErrorContains(t, err, "404") + assert.Equal(t, res, helpers.Response{}) +} diff --git a/query/QUIC.go b/query/QUIC.go index 98f29f2..e9e5dd1 100644 --- a/query/QUIC.go +++ b/query/QUIC.go @@ -7,63 +7,70 @@ import ( "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" ) type QUICResolver struct { - server string - opts Options + opts cli.Options } -func (r *QUICResolver) LookUp(msg *dns.Msg) (*dns.Msg, time.Duration, error) { - var resp Response +func (r *QUICResolver) LookUp(msg *dns.Msg) (helpers.Response, error) { + var resp helpers.Response tls := &tls.Config{ + MinVersion: tls.VersionTLS12, NextProtos: []string{"doq"}, } + + conf := new(quic.Config) + conf.HandshakeIdleTimeout = r.opts.Request.Timeout + r.opts.Logger.Debug("making DoQ request") - connection, err := quic.DialAddr(r.server, tls, nil) + connection, err := quic.DialAddr(r.opts.Request.Server, tls, conf) if err != nil { - return nil, 0, err + return helpers.Response{}, err } // Compress request to over-the-wire buf, err := msg.Pack() if err != nil { - return nil, 0, err + return helpers.Response{}, err } t := time.Now() stream, err := connection.OpenStream() if err != nil { - return nil, 0, err + return helpers.Response{}, err } _, err = stream.Write(buf) if err != nil { - return nil, 0, err + return helpers.Response{}, err } fullRes, err := io.ReadAll(stream) if err != nil { - return nil, 0, err + return helpers.Response{}, err } - resp.Answers.RTT = time.Since(t) + resp.RTT = time.Since(t) // Close with error: no error err = connection.CloseWithError(0, "") if err != nil { - return nil, 0, err + return helpers.Response{}, err } err = stream.Close() if err != nil { - return nil, 0, err + return helpers.Response{}, err } - resp.DNS = dns.Msg{} + resp.DNS = &dns.Msg{} r.opts.Logger.Debug("unpacking DoQ response") err = resp.DNS.Unpack(fullRes) if err != nil { - return nil, 0, err + return helpers.Response{}, err } - return &resp.DNS, resp.Answers.RTT, nil + return resp, nil } diff --git a/query/QUIC_test.go b/query/QUIC_test.go new file mode 100644 index 0000000..b8837d1 --- /dev/null +++ b/query/QUIC_test.go @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package query_test + +import ( + "fmt" + "net" + "strconv" + "strings" + "testing" + "time" + + "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 TestQuic(t *testing.T) { + t.Parallel() + opts := cli.Options{ + QUIC: true, + Logger: util.InitLogger(0), + Port: 853, + Request: helpers.Request{Server: "dns.adguard.com"}, + } + 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(opts) + 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) + } + msg := new(dns.Msg) + msg.SetQuestion(testCase.Name, testCase.Type) + msg = msg.SetQuestion(testCase.Name, testCase.Type) + res, err := resolver.LookUp(msg) + assert.ErrorContains(t, err, "fully qualified") + assert.Equal(t, res, helpers.Response{}) + case 1: + resolver, err := query.LoadResolver(opts) + 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, ".") { + testCase2.Name = fmt.Sprintf("%s.", testCase2.Name) + } + msg := new(dns.Msg) + msg.SetQuestion(testCase2.Name, testCase2.Type) + msg = msg.SetQuestion(testCase2.Name, testCase2.Type) + res, err := resolver.LookUp(msg) + assert.NilError(t, err) + assert.Assert(t, res != helpers.Response{}) + } + } +} + +func TestInvalidQuic(t *testing.T) { + t.Parallel() + opts := cli.Options{ + QUIC: true, + Logger: util.InitLogger(0), + Port: 853, + Request: helpers.Request{Server: "example.com", Type: dns.TypeA, Name: "git.froth.zone", Timeout: 10 * time.Millisecond}, + } + 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.ErrorContains(t, err, "timeout") + assert.Equal(t, res, helpers.Response{}) +} diff --git a/query/general.go b/query/general.go new file mode 100644 index 0000000..68a5f8d --- /dev/null +++ b/query/general.go @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package query + +import ( + "fmt" + "net" + + "git.froth.zone/sam/awl/cli" + "git.froth.zone/sam/awl/internal/helpers" + + "github.com/miekg/dns" +) + +type StandardResolver struct { + opts cli.Options +} + +func (r *StandardResolver) LookUp(msg *dns.Msg) (helpers.Response, error) { + var ( + resp helpers.Response + err error + ) + dnsClient := new(dns.Client) + dnsClient.Dialer = &net.Dialer{ + Timeout: r.opts.Request.Timeout, + } + if r.opts.TCP || r.opts.TLS { + dnsClient.Net = "tcp" + } else { + dnsClient.Net = "udp" + } + + switch { + case r.opts.IPv4: + dnsClient.Net += "4" + case r.opts.IPv6: + dnsClient.Net += "6" + } + + if r.opts.TLS { + dnsClient.Net += "-tls" + } + r.opts.Logger.Debug("Using", dnsClient.Net, "for making the request") + + resp.DNS, resp.RTT, err = dnsClient.Exchange(msg, r.opts.Request.Server) + if err != nil { + return helpers.Response{}, err + } + r.opts.Logger.Info("Request successful") + + if resp.DNS.MsgHdr.Truncated && !r.opts.Truncate { + fmt.Printf(";; Truncated, retrying with TCP\n\n") + dnsClient.Net = "tcp" + switch { + case r.opts.IPv4: + dnsClient.Net += "4" + case r.opts.IPv4: + dnsClient.Net += "6" + } + resp.DNS, resp.RTT, err = dnsClient.Exchange(msg, r.opts.Request.Server) + } + + return resp, err +} diff --git a/query/general_test.go b/query/general_test.go new file mode 100644 index 0000000..5b080b9 --- /dev/null +++ b/query/general_test.go @@ -0,0 +1,89 @@ +// 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) { + 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) + 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) { + opts := cli.Options{ + Logger: util.InitLogger(0), + IPv4: true, + Port: 5301, + Request: helpers.Request{ + Server: "madns.binarystar.systems", + Type: dns.TypeTXT, + Name: "limit.txt.example.", + }, + } + 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{}) +} + +func TestResolveAgain(t *testing.T) { + tests := []struct { + opt cli.Options + }{ + { + cli.Options{ + Logger: util.InitLogger(0), + TCP: true, + Port: 53, + Request: helpers.Request{ + Server: "8.8.4.4", + Type: dns.TypeA, + Name: "example.com.", + }, + }, + }, + { + cli.Options{ + Logger: util.InitLogger(0), + Port: 53, + Request: helpers.Request{ + Server: "8.8.4.4", + 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{}) + } + +} diff --git a/query/query.go b/query/query.go new file mode 100644 index 0000000..4111207 --- /dev/null +++ b/query/query.go @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package query + +import ( + "fmt" + + "git.froth.zone/sam/awl/cli" + "git.froth.zone/sam/awl/internal/helpers" + + "github.com/miekg/dns" +) + +func CreateQuery(opts cli.Options) (helpers.Response, error) { + var res helpers.Response + res.DNS = new(dns.Msg) + 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 + res.DNS.MsgHdr.Truncated = opts.TC + res.DNS.MsgHdr.RecursionDesired = opts.RD + res.DNS.MsgHdr.RecursionAvailable = opts.RA + res.DNS.MsgHdr.Zero = opts.Z + res.DNS.MsgHdr.AuthenticatedData = opts.AD + res.DNS.MsgHdr.CheckingDisabled = opts.CD + + if opts.DNSSEC { + res.DNS.SetEdns0(1232, true) + } + + opts.Logger.Debug(fmt.Sprintf("%+v", res)) + + resolver, err := LoadResolver(opts) + if err != nil { + return helpers.Response{}, err + } + opts.Logger.Info("Query successfully loaded") + + return resolver.LookUp(res.DNS) +} diff --git a/query/query_test.go b/query/query_test.go index e9591dd..ab00cfa 100644 --- a/query/query_test.go +++ b/query/query_test.go @@ -1,129 +1,34 @@ -package query +// SPDX-License-Identifier: BSD-3-Clause + +package query_test import ( - "fmt" - "net" - "strconv" - "strings" "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" - "github.com/stretchr/testify/assert" + "gotest.tools/v3/assert" ) -func TestResolveHTTPS(t *testing.T) { - var err error - opts := Options{ - HTTPS: true, - Logger: util.InitLogger(false), +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.", + }, } - testCase := Answers{Server: "dns9.quad9.net/dns-query", Request: dns.TypeA, Name: "git.froth.zone"} - resolver, err := LoadResolver(testCase.Server, opts) - - if !strings.HasPrefix(testCase.Server, "https://") { - testCase.Server = "https://" + testCase.Server - } - // if the domain is not canonical, make it canonical - if !strings.HasSuffix(testCase.Name, ".") { - testCase.Name = fmt.Sprintf("%s.", testCase.Name) - } - - msg := new(dns.Msg) - msg.SetQuestion(testCase.Name, testCase.Request) - msg = msg.SetQuestion(testCase.Name, testCase.Request) - var in *dns.Msg - in, testCase.RTT, err = resolver.LookUp(msg) - assert.Nil(t, err) - assert.NotNil(t, in) - -} - -func Test2ResolveHTTPS(t *testing.T) { - opts := Options{ - HTTPS: true, - Logger: util.InitLogger(false), - } - var err error - testCase := Answers{Server: "dns9.quad9.net/dns-query", Request: dns.TypeA, Name: "git.froth.zone"} - resolver, err := LoadResolver(testCase.Server, opts) - msg := new(dns.Msg) - msg.SetQuestion(testCase.Name, testCase.Request) - msg = msg.SetQuestion(testCase.Name, testCase.Request) - var in *dns.Msg - in, testCase.RTT, err = resolver.LookUp(msg) - assert.NotNil(t, err) - assert.Nil(t, in) - -} -func Test3ResolveHTTPS(t *testing.T) { - opts := Options{ - HTTPS: true, - Logger: util.InitLogger(false), - } - var err error - testCase := Answers{Server: "dns9..quad9.net/dns-query", Request: dns.TypeA, Name: "git.froth.zone."} - if !strings.HasPrefix(testCase.Server, "https://") { - testCase.Server = "https://" + testCase.Server - } - // if the domain is not canonical, make it canonical - if !strings.HasSuffix(testCase.Name, ".") { - testCase.Name = fmt.Sprintf("%s.", testCase.Name) - } - resolver, err := LoadResolver(testCase.Server, opts) - msg := new(dns.Msg) - msg.SetQuestion(testCase.Name, testCase.Request) - msg = msg.SetQuestion(testCase.Name, testCase.Request) - var in *dns.Msg - in, testCase.RTT, err = resolver.LookUp(msg) - assert.NotNil(t, err) - assert.Nil(t, in) - -} - -func TestQuic(t *testing.T) { - opts := Options{ - QUIC: true, - Logger: util.InitLogger(false), - Port: 853, - Answers: Answers{Server: "dns.adguard.com"}, - } - testCase := Answers{Server: "dns.//./,,adguard.com", Request: dns.TypeA, Name: "git.froth.zone"} - testCase2 := Answers{Server: "dns.adguard.com", Request: dns.TypeA, Name: "git.froth.zone"} - var testCases []Answers - testCases = append(testCases, testCase) - testCases = append(testCases, testCase2) - for i := range testCases { - switch i { - case 0: - resolver, err := LoadResolver(testCases[i].Server, opts) - // if the domain is not canonical, make it canonical - if !strings.HasSuffix(testCase.Name, ".") { - testCases[i].Name = fmt.Sprintf("%s.", testCases[i].Name) - } - msg := new(dns.Msg) - msg.SetQuestion(testCase.Name, testCase.Request) - msg = msg.SetQuestion(testCase.Name, testCase.Request) - var in *dns.Msg - in, testCase.RTT, err = resolver.LookUp(msg) - assert.NotNil(t, err) - assert.Nil(t, in) - case 1: - resolver, err := LoadResolver(testCase2.Server, opts) - testCase2.Server = net.JoinHostPort(testCase2.Server, strconv.Itoa(opts.Port)) - // if the domain is not canonical, make it canonical - if !strings.HasSuffix(testCase2.Name, ".") { - testCase2.Name = fmt.Sprintf("%s.", testCase2.Name) - } - msg := new(dns.Msg) - msg.SetQuestion(testCase2.Name, testCase2.Request) - msg = msg.SetQuestion(testCase2.Name, testCase2.Request) - var in *dns.Msg - in, testCase.RTT, err = resolver.LookUp(msg) - assert.Nil(t, err) - assert.NotNil(t, in) - } - - } - + 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 f1ac190..7c035ee 100644 --- a/query/resolver.go +++ b/query/resolver.go @@ -1,76 +1,50 @@ +// SPDX-License-Identifier: BSD-3-Clause + package query import ( "net" "strconv" "strings" - "time" - "git.froth.zone/sam/awl/logawl" + "git.froth.zone/sam/awl/cli" + "git.froth.zone/sam/awl/internal/helpers" "github.com/miekg/dns" ) -// represent all CLI flags -type Options struct { - Logger *logawl.Logger - - Port int - IPv4 bool - IPv6 bool - DNSSEC bool - Short bool - TCP bool - TLS bool - HTTPS bool - QUIC bool - Truncate bool - AA bool - TC bool - Z bool - CD bool - NoRD bool - NoRA bool - Reverse bool - Debug bool - Answers Answers -} -type Response struct { - Answers Answers `json:"Response"` // These be DNS query answers - DNS dns.Msg -} - -// The Answers struct is the basic structure of a DNS request -// to be returned to the user upon making a request -type Answers struct { - Server string `json:"Server"` // The server to make the DNS request from - DNS *dns.Msg - Request uint16 `json:"Request"` // The type of request - Name string `json:"Name"` // The domain name to make a DNS request for - RTT time.Duration `json:"RTT"` // The time it took to make the DNS query -} - type Resolver interface { - LookUp(*dns.Msg) (*dns.Msg, time.Duration, error) + LookUp(*dns.Msg) (helpers.Response, error) } -func LoadResolver(server string, opts Options) (Resolver, error) { - if opts.HTTPS { - opts.Logger.Debug("loading DoH resolver") - if !strings.HasPrefix(server, "https://") { - server = "https://" + server +func LoadResolver(opts cli.Options) (Resolver, error) { + switch { + case opts.HTTPS: + opts.Logger.Info("loading DNS-over-HTTPS resolver") + if !strings.HasPrefix(opts.Request.Server, "https://") { + opts.Request.Server = "https://" + opts.Request.Server } return &HTTPSResolver{ - server: server, - opts: opts, + opts: opts, }, nil - } else if opts.QUIC { - opts.Logger.Debug("loading DoQ resolver") - server = net.JoinHostPort(opts.Answers.Server, strconv.Itoa(opts.Port)) + case opts.QUIC: + opts.Logger.Info("loading DNS-over-QUIC resolver") + opts.Request.Server = net.JoinHostPort(opts.Request.Server, strconv.Itoa(opts.Port)) return &QUICResolver{ - server: server, - opts: opts, + opts: opts, + }, nil + case opts.DNSCrypt: + opts.Logger.Info("loading DNSCrypt resolver") + if !strings.HasPrefix(opts.Request.Server, "sdns://") { + opts.Request.Server = "sdns://" + opts.Request.Server + } + return &DNSCryptResolver{ + opts: opts, + }, nil + default: + opts.Logger.Info("loading standard/DNS-over-TLS resolver") + opts.Request.Server = net.JoinHostPort(opts.Request.Server, strconv.Itoa(opts.Port)) + return &StandardResolver{ + opts: opts, }, nil } - - return nil, nil } diff --git a/template.mk b/template.mk new file mode 100644 index 0000000..1698df2 --- /dev/null +++ b/template.mk @@ -0,0 +1,56 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Template for the BSD/GNU makefiles + +HASH ?= $(shell git describe --always --dirty || echo "UNKNOWN") +VER ?= "git-$(HASH)" + +CGO_ENABLED ?= 0 +GO ?= go +GOFLAGS ?= -ldflags "-s -w -X 'main.version=$(VER)'" -buildvcs=false + + +PREFIX ?= /usr/local +BIN ?= bin + +SCDOC ?= scdoc +MAN ?= $(PREFIX)/share/man + +PROG ?= awl + +# hehe +all: $(PROG) doc/$(PROG).1 + + +doc/$(PROG).1: doc/wiki/$(PROG).1.md + @cp doc/awl.1 doc/awl.bak + $(SCDOC) doc/$(PROG).1 2>/dev/null && 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 + $(GO) tool cover -func=coverage/coverage.out + $(GO) tool cover -html=coverage/coverage.out -o coverage/cover.html + +fmt: + gofmt -w -s . + +vet: + $(GO) vet ./... + +## lint: lint awl, using fmt, vet and golangci-lint +lint: fmt vet + -golangci-lint run + +## clean: clean the build files +clean: + $(GO) clean + +## help: Prints this help message +help: + @echo "Usage: " + @sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /' + +.PHONY: clean lint test fmt vet help \ No newline at end of file diff --git a/util/logger.go b/util/logger.go index 3d39476..037d4d7 100644 --- a/util/logger.go +++ b/util/logger.go @@ -1,13 +1,14 @@ +// SPDX-License-Identifier: BSD-3-Clause + package util import "git.froth.zone/sam/awl/logawl" -func InitLogger(debug bool) (Logger *logawl.Logger) { +// Initialize the logawl instance. +func InitLogger(verbosity int) (Logger *logawl.Logger) { Logger = logawl.New() - if debug { - Logger.SetLevel(3) - } + Logger.SetLevel(logawl.Level(verbosity)) return } 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/helpers.go b/util/reverseDNS.go similarity index 94% rename from util/helpers.go rename to util/reverseDNS.go index bd153e9..57d800e 100644 --- a/util/helpers.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/helpers_test.go b/util/reverseDNS_test.go similarity index 57% rename from util/helpers_test.go rename to util/reverseDNS_test.go index a53a1e4..7841e40 100644 --- a/util/helpers_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 ( @@ -15,18 +17,21 @@ var ( ) func TestIPv4(t *testing.T) { - act, err := ReverseDNS("8.8.4.4", PTR) - assert.Nil(t, err) + t.Parallel() + 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) { - act, err := ReverseDNS("2606:4700:4700::1111", PTR) - assert.Nil(t, err) + t.Parallel() + 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") } func TestNAPTR(t *testing.T) { + t.Parallel() tests := []struct { in string want string @@ -37,13 +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) { - _, err := ReverseDNS("AAAAA", 1) - assert.NotNil(t, err) + t.Parallel() + _, err := util.ReverseDNS("AAAAA", 1) + assert.ErrorContains(t, err, "no IP found") }