(refactor) Draw the rest of the owl (#38)
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #38
This commit is contained in:
parent
4b4409e464
commit
80648e08f6
56 changed files with 2338 additions and 986 deletions
|
@ -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()
|
||||
]
|
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -15,4 +15,9 @@
|
|||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
go.work
|
||||
dist/
|
||||
|
||||
# Test coverage
|
||||
coverage/*
|
||||
!coverage/.gitkeep
|
||||
|
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "doc/wiki"]
|
||||
path = doc/wiki
|
||||
url = ../awl.wiki
|
39
.goreleaser.yaml
Normal file
39
.goreleaser.yaml
Normal file
|
@ -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:'
|
24
GNUmakefile
Normal file
24
GNUmakefile
Normal file
|
@ -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
|
33
Makefile
33
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
|
||||
.PHONY: install
|
9
Mkfile
9
Mkfile
|
@ -1,9 +0,0 @@
|
|||
GO=GO
|
||||
awl: awl.go
|
||||
$GO build -o awl
|
||||
|
||||
install:
|
||||
$GO install .
|
||||
|
||||
test:
|
||||
$GO test ./...
|
101
README.md
101
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?
|
||||
# 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)
|
||||
|
|
17
awl.go
17
awl.go
|
@ -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)
|
||||
}
|
||||
}
|
216
cli.go
216
cli.go
|
@ -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]
|
||||
<name> can be a name or an IP address
|
||||
<record> 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: "`<port>` 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
|
||||
}
|
170
cli/cli.go
Normal file
170
cli/cli.go
Normal file
|
@ -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]
|
||||
<name> domain, IP address, phone number
|
||||
<record> 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
|
||||
}
|
99
cli/cli_test.go
Normal file
99
cli/cli_test.go
Normal file
|
@ -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")
|
||||
})
|
||||
}
|
117
cli/dig.go
Normal file
117
cli/dig.go
Normal file
|
@ -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
|
||||
}
|
55
cli/dig_test.go
Normal file
55
cli/dig_test.go
Normal file
|
@ -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")
|
||||
}
|
||||
})
|
||||
}
|
5
cli/docs.go
Normal file
5
cli/docs.go
Normal file
|
@ -0,0 +1,5 @@
|
|||
/*
|
||||
The CLI part of the package, including both POSIX
|
||||
flag parsing and dig-like flag parsing.
|
||||
*/
|
||||
package cli
|
150
cli/misc.go
Normal file
150
cli/misc.go
Normal file
|
@ -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
|
||||
}
|
166
cli/misc_test.go
Normal file
166
cli/misc_test.go
Normal file
|
@ -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)
|
||||
})
|
||||
}
|
55
cli/options.go
Normal file
55
cli/options.go
Normal file
|
@ -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")
|
126
cli_test.go
126
cli_test.go
|
@ -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)
|
||||
})
|
||||
}
|
|
@ -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'
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
21
conf/unix_test.go
Normal file
21
conf/unix_test.go
Normal file
|
@ -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)
|
||||
}
|
21
conf/win_test.go
Normal file
21
conf/win_test.go
Normal file
|
@ -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)
|
||||
}
|
0
coverage/.gitkeep
Normal file
0
coverage/.gitkeep
Normal file
244
doc/awl.1
Normal file
244
doc/awl.1
Normal file
|
@ -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
|
1
doc/wiki
Submodule
1
doc/wiki
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 0fba1fbe4b12e8c88514b3f7d98be3e75a5a034d
|
20
go.mod
20
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
|
||||
|
|
81
go.sum
81
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=
|
||||
|
|
4
internal/helpers/docs.go
Normal file
4
internal/helpers/docs.go
Normal file
|
@ -0,0 +1,4 @@
|
|||
/*
|
||||
Useful structs used everywhere I couldn't find a better place to shove
|
||||
*/
|
||||
package helpers
|
25
internal/helpers/query.go
Normal file
25
internal/helpers/query.go
Normal file
|
@ -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
|
||||
}
|
13
internal/helpers/query_test.go
Normal file
13
internal/helpers/query_test.go
Normal file
|
@ -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)
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <LogLevel> YYYY/MM/DD HH:MM:SS (local time) <the message to log>
|
||||
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...)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
130
main.go
Normal file
130
main.go
Normal file
|
@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
30
mkfile
Normal file
30
mkfile
Normal file
|
@ -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
|
199
query.go
199
query.go
|
@ -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
|
||||
}
|
58
query/DNSCrypt.go
Normal file
58
query/DNSCrypt.go
Normal file
|
@ -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
|
||||
}
|
52
query/DNSCrypt_test.go
Normal file
52
query/DNSCrypt_test.go
Normal file
|
@ -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{})
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
105
query/HTTPS_test.go
Normal file
105
query/HTTPS_test.go
Normal file
|
@ -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{})
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
84
query/QUIC_test.go
Normal file
84
query/QUIC_test.go
Normal file
|
@ -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{})
|
||||
}
|
65
query/general.go
Normal file
65
query/general.go
Normal file
|
@ -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
|
||||
}
|
89
query/general_test.go
Normal file
89
query/general_test.go
Normal file
|
@ -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{})
|
||||
}
|
||||
|
||||
}
|
42
query/query.go
Normal file
42
query/query.go
Normal file
|
@ -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)
|
||||
}
|
|
@ -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{})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
56
template.mk
Normal file
56
template.mk
Normal file
|
@ -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/wiki/$(PROG).1.md >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
|
|
@ -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
|
||||
}
|
||||
|
|
16
util/logger_test.go
Normal file
16
util/logger_test.go
Normal file
|
@ -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))
|
||||
}
|
|
@ -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)
|
|
@ -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")
|
||||
}
|
Loading…
Reference in a new issue