Minor (complete) refactor #38
35 changed files with 1363 additions and 856 deletions
|
@ -1,22 +1,67 @@
|
|||
local pipeline(version, arch) = {
|
||||
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 -v ./... -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 -v ./... -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()
|
||||
]
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -15,4 +15,5 @@
|
|||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
go.work
|
||||
dist/
|
||||
|
|
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
Makefile
24
Makefile
|
@ -1,7 +1,12 @@
|
|||
GO:=go
|
||||
GOFLAGS:= -ldflags '-s -w'
|
||||
PREFIX:=/usr/local
|
||||
BINPATH=$(PREFIX)/bin
|
||||
# This should only be used for dev environments
|
||||
GO := go
|
||||
GOFLAGS := -ldflags '-s -w'
|
||||
PREFIX := /usr/local
|
||||
BINPATH = $(PREFIX)/bin
|
||||
export CGO_ENABLED=1
|
||||
export CC=gcc
|
||||
|
||||
.PHONY: clean doc
|
||||
|
||||
# hehe
|
||||
all: awl
|
||||
|
@ -9,19 +14,22 @@ all: awl
|
|||
awl: .
|
||||
$(GO) build -o awl $(GOFLAGS) .
|
||||
|
||||
doc:
|
||||
scdoc < doc/awl.1.md > doc/awl.1
|
||||
|
||||
test:
|
||||
$(GO) test ./...
|
||||
$(GO) test -race -v ./... -cover
|
||||
|
||||
fmt:
|
||||
$(GO) fmt
|
||||
gofmt -w -s .
|
||||
|
||||
vet:
|
||||
$(GO) vet
|
||||
$(GO) vet ./...
|
||||
|
||||
lint: fmt vet
|
||||
|
||||
install: awl
|
||||
install awl $(BINPATH) || echo "You probably need to run `sudo make install`"
|
||||
install awl $(BINPATH)
|
||||
|
||||
clean:
|
||||
$(GO) clean
|
24
README.md
24
README.md
|
@ -12,24 +12,14 @@ This was made as my first major experiment with Go, so there are probably things
|
|||
The excellent [dns](https://github.com/miekg/dns) library for Go does most of the heavy
|
||||
lifting.
|
||||
|
||||
## What works
|
||||
## What awl should do
|
||||
|
||||
- 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?
|
||||
- Making a drop-in replacement for drill?
|
||||
- What about dig?
|
||||
|
||||
## What awl won't do
|
||||
- Print to files or read from files
|
||||
- Colour outputs (unless?)
|
||||
|
|
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
|
||||
}
|
135
cli/cli.go
Normal file
135
cli/cli.go
Normal file
|
@ -0,0 +1,135 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"git.froth.zone/sam/awl/internal/structs"
|
||||
"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.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()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// CLI flag
|
||||
var (
|
||||
port = flag.Int("port", 0, "`port` to make DNS query (default: 53 for UDP/TCP, 853 for TLS/QUIC", flag.OptShorthand('p'), flag.OptDisablePrintDefault(true))
|
||||
query = flag.String("query", ".", "domain name to query", flag.OptShorthand('q'))
|
||||
class = flag.String("class", "IN", "DNS class to query", flag.OptShorthand('c'))
|
||||
qType = flag.String("qType", "A", "type to query", flag.OptShorthand('t'))
|
||||
|
||||
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'))
|
||||
|
||||
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")
|
||||
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: set)", flag.OptDisablePrintDefault(true))
|
||||
rd = flag.Bool("rd", true, "set/unset RD (Recursion Desired) flag (default: set)", flag.OptDisablePrintDefault(true))
|
||||
ra = flag.Bool("ra", false, "set/unset RA (Recursion Available) flag (default: set)", flag.OptDisablePrintDefault(true))
|
||||
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'))
|
||||
|
||||
verbosity = flag.Int("verbosity", 0, "sets verbosity", flag.OptShorthand('v'), flag.OptNoOptDefVal("3"))
|
||||
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
|
||||
flag.Parse()
|
||||
|
||||
opts := Options{
|
||||
Logger: util.InitLogger(*verbosity),
|
||||
Class: dns.StringToClass[*class],
|
||||
Port: *port,
|
||||
IPv4: *ipv4,
|
||||
IPv6: *ipv6,
|
||||
DNSSEC: *dnssec,
|
||||
Short: *short,
|
||||
TCP: *tcp,
|
||||
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: structs.Request{
|
||||
Type: dns.StringToType[*qType],
|
||||
Name: *query,
|
||||
},
|
||||
}
|
||||
|
||||
if *versionFlag {
|
||||
fmt.Printf("awl version %s, built with %s\n", version, runtime.Version())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// 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.Fatal(err)
|
||||
}
|
||||
|
||||
if opts.Port == 0 {
|
||||
if opts.TLS || opts.QUIC {
|
||||
opts.Port = 853
|
||||
} else {
|
||||
opts.Port = 53
|
||||
}
|
||||
}
|
||||
|
||||
// opts.Request = req
|
||||
|
||||
// Set verbosity to full if just -v is specified
|
||||
// flag.Lookup("verbosity").NoOptDefVal = "3"
|
||||
|
||||
return opts, nil
|
||||
}
|
159
cli/cli_test.go
Normal file
159
cli/cli_test.go
Normal file
|
@ -0,0 +1,159 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli_test
|
||||
|
||||
// TODO: readd these
|
||||
|
||||
// import (
|
||||
// "os"
|
||||
// "testing"
|
||||
|
||||
// "git.froth.zone/sam/awl/internal/structs"
|
||||
// "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) {
|
||||
// t.Parallel()
|
||||
// app := prepareCLI()
|
||||
// // What more can even be done lmao
|
||||
// require.NotNil(t, app)
|
||||
// }
|
||||
|
||||
// func TestArgParse(t *testing.T) {
|
||||
// t.Parallel()
|
||||
// tests := []struct {
|
||||
// in []string
|
||||
// want structs.Request
|
||||
// }{
|
||||
// {
|
||||
// []string{"@::1", "localhost", "AAAA"},
|
||||
// structs.Request{Server: "::1", Type: dns.TypeAAAA, Name: "localhost"},
|
||||
// },
|
||||
// {
|
||||
// []string{"@1.0.0.1", "google.com"},
|
||||
// structs.Request{Server: "1.0.0.1", Type: dns.TypeA, Name: "google.com"},
|
||||
// },
|
||||
// {
|
||||
// []string{"@8.8.4.4"},
|
||||
// structs.Request{Server: "8.8.4.4", Type: 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 TestTLS(t *testing.T) {
|
||||
// app := prepareCLI()
|
||||
// args := os.Args[0:1]
|
||||
// args = append(args, "-T")
|
||||
// args = append(args, "git.froth.zone")
|
||||
// err := app.Run(args)
|
||||
// assert.Nil(t, err)
|
||||
// }
|
||||
|
||||
// func TestXML(t *testing.T) {
|
||||
// app := prepareCLI()
|
||||
// args := os.Args[0:1]
|
||||
// args = append(args, "-X")
|
||||
// args = append(args, "git.froth.zone")
|
||||
// err := app.Run(args)
|
||||
// assert.Nil(t, err)
|
||||
// }
|
||||
|
||||
// func TestYAML(t *testing.T) {
|
||||
// app := prepareCLI()
|
||||
// args := os.Args[0:1]
|
||||
// args = append(args, "-y")
|
||||
// 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 {
|
||||
// require.ErrorContains(t, err, "domain must be fully qualified")
|
||||
// }
|
||||
// require.Nil(t, err)
|
||||
// })
|
||||
// }
|
57
cli/dig.go
Normal file
57
cli/dig.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Parse dig-like commands and set the options as such
|
||||
func ParseDig(arg string, opts *Options) error {
|
||||
// returns true if the flag starts with a no
|
||||
isNo := !strings.HasPrefix(arg, "no")
|
||||
switch arg {
|
||||
// Set DNS query flags
|
||||
case "aaflag", "aaonly", "noaaflag", "noaaonly":
|
||||
opts.AA = isNo
|
||||
case "adflag", "noadflag":
|
||||
opts.AD = isNo
|
||||
case "cdflag", "nocdflag":
|
||||
opts.CD = isNo
|
||||
case "qrflag", "noqrflag":
|
||||
opts.QR = isNo
|
||||
case "raflag", "noraflag":
|
||||
opts.RA = isNo
|
||||
case "rdflag", "recurse", "nordflag", "norecurse":
|
||||
opts.RD = isNo
|
||||
case "tcflag", "notcflag":
|
||||
opts.TC = isNo
|
||||
case "zflag", "nozflag":
|
||||
opts.Z = isNo
|
||||
// End DNS query flags
|
||||
|
||||
case "dnssec", "nodnssec":
|
||||
opts.DNSSEC = isNo
|
||||
case "tcp", "vc", "notcp", "novc":
|
||||
opts.TCP = isNo
|
||||
case "ignore", "noignore":
|
||||
// Invert (ignore truncation when true)
|
||||
opts.Truncate = !isNo
|
||||
|
||||
// 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
|
||||
|
||||
default:
|
||||
return fmt.Errorf("dig: Unknown flag given")
|
||||
}
|
||||
return nil
|
||||
}
|
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
|
122
cli/misc.go
Normal file
122
cli/misc.go
Normal file
|
@ -0,0 +1,122 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
|
||||
"git.froth.zone/sam/awl/conf"
|
||||
"git.froth.zone/sam/awl/internal/structs"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
// Parse the wildcard arguments, drill style
|
||||
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, "@"):
|
||||
opts.Request.Server = arg[1:]
|
||||
case strings.Contains(arg, "."):
|
||||
opts.Query, err = idna.ToASCII(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case ok:
|
||||
// If it's a DNS request, it's a DNS request (obviously)
|
||||
opts.Type = r
|
||||
case strings.HasPrefix(arg, "+"):
|
||||
// Dig-style +queries
|
||||
err = ParseDig(strings.ToLower(arg[1:]), opts)
|
||||
default:
|
||||
//else, assume it's a name
|
||||
opts.Query, err = idna.ToASCII(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing was set, set a default
|
||||
if opts.Query == "" {
|
||||
opts.Query = "."
|
||||
if opts.Type == 0 {
|
||||
opts.Type = dns.StringToType["NS"]
|
||||
}
|
||||
} else {
|
||||
if opts.Type == 0 {
|
||||
opts.Type = dns.StringToType["A"]
|
||||
}
|
||||
}
|
||||
if opts.Request.Server == "" {
|
||||
switch {
|
||||
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.Request.Server = "9.9.9.9"
|
||||
} else {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make reverse adresses proper addresses
|
||||
if opts.Reverse {
|
||||
if dns.TypeToString[opts.Request.Type] == "A" {
|
||||
opts.Type = dns.StringToType["PTR"]
|
||||
}
|
||||
opts.Query, err = util.ReverseDNS(opts.Query, opts.Request.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Class == 0 {
|
||||
opts.Class = dns.StringToClass["IN"]
|
||||
}
|
||||
|
||||
// if the domain is not canonical, make it canonical
|
||||
if !strings.HasSuffix(opts.Query, ".") {
|
||||
opts.Query = fmt.Sprintf("%s.", opts.Query)
|
||||
}
|
||||
|
||||
opts.Request = structs.Request{
|
||||
Server: opts.Request.Server,
|
||||
Type: opts.Type,
|
||||
Class: opts.Class,
|
||||
Name: opts.Query,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
41
cli/options.go
Normal file
41
cli/options.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"git.froth.zone/sam/awl/internal/structs"
|
||||
"git.froth.zone/sam/awl/logawl"
|
||||
)
|
||||
|
||||
// CLI options structure
|
||||
type Options struct {
|
||||
Logger *logawl.Logger // Logger
|
||||
Port int // DNS port
|
||||
Query string // DNS Query
|
||||
Class uint16 // DNS Class
|
||||
Type uint16 // DNS Type
|
||||
IPv4 bool // Force IPv4
|
||||
IPv6 bool // Force IPv6
|
||||
DNSSEC bool // Enable DNSSEC
|
||||
TCP bool // Query with TCP
|
||||
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
|
||||
Short bool // Short output
|
||||
JSON bool // Outout as JSON
|
||||
XML bool // Output as XML
|
||||
YAML bool // Output at YAML
|
||||
|
||||
Request structs.Request
|
||||
}
|
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)
|
||||
})
|
||||
}
|
|
@ -9,6 +9,7 @@ import (
|
|||
)
|
||||
|
||||
func TestGetPlan9Config(t *testing.T) {
|
||||
t.Parallel()
|
||||
ndbs := []struct {
|
||||
in string
|
||||
want string
|
||||
|
|
168
doc/awl.1
Normal file
168
doc/awl.1
Normal file
|
@ -0,0 +1,168 @@
|
|||
.\" 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-20"
|
||||
.PP
|
||||
.SH NAME
|
||||
awl - drill, writ small
|
||||
.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 tannery (leatherworking).\&
|
||||
.PP
|
||||
Written in Go, \fBawl\fR is designed to be a more "modern" version of \fIdrill\fR(1) by including some more RFCs and output options.\&
|
||||
\fBawl\fR is still heavily Work-In-Progress so some features may get added or removed.\&
|
||||
.PP
|
||||
.SH OPTIONS
|
||||
.RS 4
|
||||
\fB-D\fR, \fB--dnssec\fR
|
||||
.br
|
||||
Enable DNSSEC.\& This needs to be manually enabled.\&
|
||||
.PP
|
||||
\fB--debug\fR
|
||||
.br
|
||||
Enable debug logging (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--no-truncate\fR
|
||||
.br
|
||||
Ignore UDP truncation (by default, awl \fIretries with TCP\fR)
|
||||
.PP
|
||||
\fB-t\fR, \fB--tcp\fR
|
||||
.br
|
||||
Use TCP for the query (see \fIRFC 7766\fR)
|
||||
.PP
|
||||
\fB-u\fR, \fB--udp\fR
|
||||
.br
|
||||
Use UDP for the query (default)
|
||||
.PP
|
||||
\fB-T\fR, \fB--tls\fR
|
||||
.br
|
||||
Use DNS-over-TLS, implies \fB-t\fR (see \fIRFC 7858\fR)
|
||||
.PP
|
||||
\fB-H\fR.\& \fB--https\fR
|
||||
.br
|
||||
Use DNS-over-HTTPS (see \fIRFC 8484\fR)
|
||||
.PP
|
||||
\fB-Q\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
|
||||
.RE
|
||||
.SS DNS Flags
|
||||
.PP
|
||||
.RS 4
|
||||
\fB--aa\fR
|
||||
.br
|
||||
\fISET\fR Authoritative Answer (default: unset)
|
||||
.PP
|
||||
\fB--ad\fR
|
||||
.br
|
||||
\fISET\fR Authenticated Data (default: unset)
|
||||
.PP
|
||||
\fB--tc\fR
|
||||
.br
|
||||
\fISET\fR TC (TrunCated) flag (default: not set)
|
||||
.PP
|
||||
\fB-z\fR
|
||||
.br
|
||||
\fISET\fR Z (Zero) flag (default: not set)
|
||||
.PP
|
||||
\fB--cd\fR
|
||||
.br
|
||||
\fISET\fR CD (Checking Disabled) flag (default: not set)
|
||||
.PP
|
||||
\fB--no-qr\fR
|
||||
.br
|
||||
\fIUNSET\fR QR (QueRy) flag (default: set)
|
||||
.PP
|
||||
\fB--no-rd\fR
|
||||
.br
|
||||
\fIUNSET\fR RD (Recursion Desired) flag (default: set)
|
||||
.PP
|
||||
\fB--no-ra\fR
|
||||
.br
|
||||
\fIUNSET\fR RA (Recursion Available) flag (default: set)
|
||||
.PP
|
||||
.RE
|
||||
.SS Output Options
|
||||
.RS 4
|
||||
\fB-j\fR, \fB--json\fR
|
||||
.br
|
||||
Print the query results as JSON.\&
|
||||
.PP
|
||||
\fB-X\fR, \fB--xml\fR
|
||||
.br
|
||||
Print the query results as XML.\&
|
||||
.PP
|
||||
\fB-y\fR, \fB--yaml\fR
|
||||
.br
|
||||
Print the query results as YAML.\&
|
||||
.PP
|
||||
\fB-s\fR, \fB--short\fR
|
||||
.br
|
||||
Print just the results.\&
|
||||
.br
|
||||
Equivalent to \fBdig +short\fR
|
||||
.PP
|
||||
.RE
|
||||
.SH SEE ALSO
|
||||
\fIdrill\fR(1), \fIdig\fR(1), the many DNS RFCs
|
115
doc/awl.1.md
Normal file
115
doc/awl.1.md
Normal file
|
@ -0,0 +1,115 @@
|
|||
AWL(1)
|
||||
|
||||
# NAME
|
||||
awl - drill, writ small
|
||||
|
||||
# SYNOPSIS
|
||||
*awl* [ _OPTIONS_ ] _name_ [ _@server_ ] [ _type_ ]++
|
||||
where
|
||||
|
||||
_name_ is the query to make (*example: froth.zone*)++
|
||||
_@server_ is the server to query (*example: dns.froth.zone*)++
|
||||
_type_ is the DNS resource type (*example: AAAA*)
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
*awl* (*a*wls *w*ant *l*icorice) is a simple tool designed to make DNS queries, much like the venerable _dig_(1).
|
||||
An awl is a tool used to make small holes, typically used in tannery (leatherworking).
|
||||
|
||||
Written in Go, *awl* is designed to be a more "modern" version of _drill_(1) by including some more RFCs and output options.
|
||||
*awl* is still heavily Work-In-Progress so some features may get added or removed.
|
||||
|
||||
# OPTIONS
|
||||
_Dig-like queries are supported, see dig(1)_
|
||||
|
||||
*-D*, *--dnssec*++
|
||||
Enable DNSSEC. This needs to be manually enabled.
|
||||
|
||||
*--debug*++
|
||||
Enable debug logging (currently WIP).
|
||||
|
||||
*-v*++
|
||||
Print the version and exit.
|
||||
|
||||
*-h*++
|
||||
Show a "short" help message.
|
||||
|
||||
## Query Options
|
||||
*-4*++
|
||||
Only make query over IPv4
|
||||
|
||||
*-6*++
|
||||
Only make query over IPv6
|
||||
|
||||
*-p*, *--port* _port_++
|
||||
Sets the port to query.++
|
||||
++
|
||||
_Default Ports_:
|
||||
- _53_ for *UDP* and *TCP*
|
||||
- _853_ for *TLS* and *QUIC*
|
||||
- _443_ for *HTTPS*
|
||||
|
||||
*--no-truncate*++
|
||||
Ignore UDP truncation (by default, awl _retries with TCP_)
|
||||
|
||||
*-t*, *--tcp*++
|
||||
Use TCP for the query (see _RFC 7766_)
|
||||
|
||||
*-u*, *--udp*++
|
||||
Use UDP for the query (default)
|
||||
|
||||
*-T*, *--tls*++
|
||||
Use DNS-over-TLS, implies *-t* (see _RFC 7858_)
|
||||
|
||||
*-H*. *--https*++
|
||||
Use DNS-over-HTTPS (see _RFC 8484_)
|
||||
|
||||
*-Q*. *--quic*++
|
||||
Use DNS-over-QUIC (see _RFC 9250_)
|
||||
|
||||
*-x*, *--reverse*++
|
||||
Do a reverse lookup. Sets default _type_ to PTR.++
|
||||
*awl* automatically makes an IP or phone number canonical.
|
||||
|
||||
## DNS Flags
|
||||
|
||||
*--aa*++
|
||||
_SET_ Authoritative Answer (default: unset)
|
||||
|
||||
*--ad*++
|
||||
_SET_ Authenticated Data (default: unset)
|
||||
|
||||
*--tc*++
|
||||
_SET_ TC (TrunCated) flag (default: not set)
|
||||
|
||||
*-z*++
|
||||
_SET_ Z (Zero) flag (default: not set)
|
||||
|
||||
*--cd*++
|
||||
_SET_ CD (Checking Disabled) flag (default: not set)
|
||||
|
||||
*--no-qr*++
|
||||
_UNSET_ QR (QueRy) flag (default: set)
|
||||
|
||||
*--no-rd*++
|
||||
_UNSET_ RD (Recursion Desired) flag (default: set)
|
||||
|
||||
*--no-ra*++
|
||||
_UNSET_ RA (Recursion Available) flag (default: set)
|
||||
|
||||
## Output Options
|
||||
*-j*, *--json*++
|
||||
Print the query results as JSON.
|
||||
|
||||
*-X*, *--xml*++
|
||||
Print the query results as XML.
|
||||
|
||||
*-y*, *--yaml*++
|
||||
Print the query results as YAML.
|
||||
|
||||
*-s*, *--short*++
|
||||
Print just the results.++
|
||||
Equivalent to *dig +short*
|
||||
|
||||
# SEE ALSO
|
||||
_drill_(1), _dig_(1), the many DNS RFCs
|
7
go.mod
7
go.mod
|
@ -5,19 +5,20 @@ go 1.18
|
|||
require (
|
||||
github.com/lucas-clemente/quic-go v0.28.0
|
||||
github.com/miekg/dns v1.1.50
|
||||
github.com/urfave/cli/v2 v2.11.0
|
||||
github.com/stefansundin/go-zflag v1.1.1
|
||||
golang.org/x/net v0.0.0-20220708220712-1185a9018129
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // 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
|
||||
|
@ -25,9 +26,7 @@ require (
|
|||
github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/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
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8
|
||||
|
|
39
go.sum
39
go.sum
|
@ -8,7 +8,6 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1
|
|||
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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
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 +16,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=
|
||||
|
@ -79,8 +76,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/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
|
@ -124,10 +119,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=
|
||||
|
@ -153,6 +145,8 @@ 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/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=
|
||||
|
@ -161,17 +155,10 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||
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/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=
|
||||
|
@ -181,7 +168,6 @@ golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACk
|
|||
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-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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -206,18 +192,10 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
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/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
|
@ -253,21 +231,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
|
23
internal/structs/query.go
Normal file
23
internal/structs/query.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package structs
|
||||
|
||||
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
|
||||
}
|
|
@ -28,15 +28,27 @@ 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
|
||||
err := l.Printer(0, fmt.Sprintln(v...)) //Fatal level
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
os.Exit(1)
|
||||
case 1:
|
||||
l.Printer(1, fmt.Sprintln(v...)) //Error level
|
||||
err := l.Printer(1, fmt.Sprintln(v...)) //Error level
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
os.Exit(2)
|
||||
case 2:
|
||||
l.Printer(2, fmt.Sprintln(v...)) //Info level
|
||||
err := l.Printer(2, fmt.Sprintln(v...)) //Info level
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case 3:
|
||||
l.Printer(3, fmt.Sprintln(v...)) //Debug level
|
||||
err := l.Printer(3, fmt.Sprintln(v...)) //Debug level
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -44,7 +56,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,7 +92,7 @@ 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)
|
||||
l.FormatHeader(&l.buf, now, line, level)
|
||||
l.buf = append(l.buf, s...)
|
||||
if len(s) == 0 || s[len(s)-1] != '\n' {
|
||||
l.buf = append(l.buf, '\n')
|
||||
|
|
|
@ -1,52 +1,59 @@
|
|||
package logawl
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package logawl_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/logawl"
|
||||
|
||||
"github.com/stretchr/testify/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)
|
||||
assert.Equal(t, logawl.Level(2), logger.Level) //cast 2 (int) to 2 (level)
|
||||
|
||||
//Validate setting and getting levels from memory works
|
||||
for i := range 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))
|
||||
for i := range logawl.AllLevels {
|
||||
m[i], err = logger.UnMarshalLevel(logawl.Level(i))
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
//iterate over map and assert equal
|
||||
for i := range AllLevels {
|
||||
lv, err := logger.UnMarshalLevel(Level(i))
|
||||
for i := range logawl.AllLevels {
|
||||
lv, err := logger.UnMarshalLevel(logawl.Level(i))
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, m[i], lv)
|
||||
}
|
||||
|
||||
lv, err := logger.UnMarshalLevel(Level(9001))
|
||||
lv, err := logger.UnMarshalLevel(logawl.Level(9001))
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "", lv)
|
||||
assert.ErrorContains(t, err, "invalid log level choice")
|
||||
}
|
||||
|
||||
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:
|
||||
|
@ -76,8 +83,9 @@ func TestLogger(t *testing.T) {
|
|||
}
|
||||
|
||||
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.NotNil(t, logger.FormatHeader(&test, ti, 0, logawl.Level(9001))) //make sure error is error
|
||||
|
||||
}
|
||||
|
|
71
main.go
Normal file
71
main.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/query"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var version = "DEV"
|
||||
|
||||
func main() {
|
||||
opts, err := cli.ParseCLI(version)
|
||||
if err != nil {
|
||||
opts.Logger.Fatal(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
resp, err := query.CreateQuery(opts)
|
||||
if err != nil {
|
||||
opts.Logger.Fatal(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
switch {
|
||||
case opts.JSON:
|
||||
json, err := json.MarshalIndent(resp.DNS, "", " ")
|
||||
if err != nil {
|
||||
opts.Logger.Fatal(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(string(json))
|
||||
case opts.XML:
|
||||
xml, err := xml.MarshalIndent(resp.DNS, "", " ")
|
||||
if err != nil {
|
||||
opts.Logger.Fatal(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(string(xml))
|
||||
case opts.YAML:
|
||||
yaml, err := yaml.Marshal(resp.DNS)
|
||||
if err != nil {
|
||||
opts.Logger.Fatal(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(string(yaml))
|
||||
default:
|
||||
if !opts.Short {
|
||||
// Print everything
|
||||
fmt.Println(resp.DNS)
|
||||
fmt.Println(";; Query time:", resp.RTT)
|
||||
fmt.Println(";; SERVER:", opts.Request.Server)
|
||||
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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
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
|
||||
}
|
|
@ -9,53 +9,55 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/structs"
|
||||
"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
|
||||
func (r *HTTPSResolver) LookUp(msg *dns.Msg) (structs.Response, error) {
|
||||
var resp structs.Response
|
||||
httpR := &http.Client{}
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return structs.Response{}, err
|
||||
}
|
||||
r.opts.Logger.Debug("making DoH request")
|
||||
// query := server + "?dns=" + base64.RawURLEncoding.EncodeToString(buf)
|
||||
req, err := http.NewRequest("POST", r.server, bytes.NewBuffer(buf))
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("DoH: %s", err.Error())
|
||||
return structs.Response{}, fmt.Errorf("DoH: %s", err.Error())
|
||||
}
|
||||
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 structs.Response{}, fmt.Errorf("DoH HTTP request error: %s", err.Error())
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, 0, fmt.Errorf("DoH server responded with HTTP %d", res.StatusCode)
|
||||
return structs.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 structs.Response{}, fmt.Errorf("DoH body read error: %s", err.Error())
|
||||
}
|
||||
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 structs.Response{}, fmt.Errorf("DoH dns message unpack error: %s", err.Error())
|
||||
}
|
||||
|
||||
return &resp.DNS, resp.Answers.RTT, nil
|
||||
return resp, nil
|
||||
}
|
||||
|
|
89
query/HTTPS_test.go
Normal file
89
query/HTTPS_test.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
// 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/structs"
|
||||
"git.froth.zone/sam/awl/query"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestResolveHTTPS(t *testing.T) {
|
||||
t.Parallel()
|
||||
var err error
|
||||
opts := cli.Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(0),
|
||||
}
|
||||
testCase := structs.Request{Server: "dns9.quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone"}
|
||||
resolver, err := query.LoadResolver(testCase.Server, opts)
|
||||
assert.Nil(t, err)
|
||||
|
||||
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.Type)
|
||||
msg = msg.SetQuestion(testCase.Name, testCase.Type)
|
||||
res, err := resolver.LookUp(msg)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, res)
|
||||
|
||||
}
|
||||
|
||||
func Test2ResolveHTTPS(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := cli.Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(0),
|
||||
}
|
||||
var err error
|
||||
testCase := structs.Request{Server: "dns9.quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone"}
|
||||
resolver, err := query.LoadResolver(testCase.Server, opts)
|
||||
assert.Nil(t, err)
|
||||
msg := new(dns.Msg)
|
||||
msg.SetQuestion(testCase.Name, testCase.Type)
|
||||
msg = msg.SetQuestion(testCase.Name, testCase.Type)
|
||||
res, err := resolver.LookUp(msg)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, res, structs.Response{})
|
||||
|
||||
}
|
||||
func Test3ResolveHTTPS(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := cli.Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(0),
|
||||
}
|
||||
var err error
|
||||
testCase := structs.Request{Server: "dns9..quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone."}
|
||||
if !strings.HasPrefix(testCase.Server, "https://") {
|
||||
testCase.Server = "https://" + testCase.Server
|
||||
}
|
||||
// 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(testCase.Server, opts)
|
||||
assert.Nil(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.Error(t, err)
|
||||
assert.Equal(t, res, structs.Response{})
|
||||
|
||||
}
|
|
@ -7,63 +7,67 @@ import (
|
|||
"io"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/structs"
|
||||
|
||||
"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) (structs.Response, error) {
|
||||
var resp structs.Response
|
||||
tls := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
NextProtos: []string{"doq"},
|
||||
}
|
||||
r.opts.Logger.Debug("making DoQ request")
|
||||
connection, err := quic.DialAddr(r.server, tls, nil)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return structs.Response{}, err
|
||||
}
|
||||
|
||||
// Compress request to over-the-wire
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return structs.Response{}, err
|
||||
}
|
||||
t := time.Now()
|
||||
stream, err := connection.OpenStream()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return structs.Response{}, err
|
||||
}
|
||||
_, err = stream.Write(buf)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return structs.Response{}, err
|
||||
}
|
||||
|
||||
fullRes, err := io.ReadAll(stream)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return structs.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 structs.Response{}, err
|
||||
}
|
||||
|
||||
err = stream.Close()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return structs.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 structs.Response{}, err
|
||||
}
|
||||
return &resp.DNS, resp.Answers.RTT, nil
|
||||
return resp, nil
|
||||
}
|
||||
|
|
67
query/QUIC_test.go
Normal file
67
query/QUIC_test.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
// 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/structs"
|
||||
"git.froth.zone/sam/awl/query"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestQuic(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := cli.Options{
|
||||
QUIC: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 853,
|
||||
Request: structs.Request{Server: "dns.adguard.com"},
|
||||
}
|
||||
testCase := structs.Request{Server: "dns.//./,,adguard.com", Type: dns.TypeA, Name: "git.froth.zone"}
|
||||
testCase2 := structs.Request{Server: "dns.adguard.com", Type: dns.TypeA, Name: "git.froth.zone"}
|
||||
var testCases []structs.Request
|
||||
testCases = append(testCases, testCase)
|
||||
testCases = append(testCases, testCase2)
|
||||
for i := range testCases {
|
||||
switch i {
|
||||
case 0:
|
||||
resolver, err := query.LoadResolver(testCases[i].Server, opts)
|
||||
assert.Nil(t, err)
|
||||
// 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.NotNil(t, err)
|
||||
assert.Equal(t, res, structs.Response{})
|
||||
case 1:
|
||||
resolver, err := query.LoadResolver(testCase2.Server, opts)
|
||||
assert.Nil(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.Nil(t, err)
|
||||
assert.NotNil(t, res)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
60
query/general.go
Normal file
60
query/general.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/structs"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type StandardResolver struct {
|
||||
server string
|
||||
opts cli.Options
|
||||
}
|
||||
|
||||
func (r *StandardResolver) LookUp(msg *dns.Msg) (structs.Response, error) {
|
||||
var (
|
||||
resp structs.Response
|
||||
err error
|
||||
)
|
||||
dnsClient := new(dns.Client)
|
||||
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"
|
||||
}
|
||||
|
||||
resp.DNS, resp.RTT, err = dnsClient.Exchange(msg, r.server)
|
||||
if err != nil {
|
||||
return structs.Response{}, err
|
||||
}
|
||||
|
||||
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.server)
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
34
query/query.go
Normal file
34
query/query.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/structs"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func CreateQuery(opts cli.Options) (structs.Response, error) {
|
||||
var res structs.Response
|
||||
res.DNS = new(dns.Msg)
|
||||
res.DNS.SetQuestion(opts.Query, opts.Type)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
resolver, err := LoadResolver(opts.Request.Server, opts)
|
||||
if err != nil {
|
||||
return structs.Response{}, err
|
||||
}
|
||||
|
||||
return resolver.LookUp(res.DNS)
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestResolveHTTPS(t *testing.T) {
|
||||
var err error
|
||||
opts := Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(false),
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,60 +1,24 @@
|
|||
// 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/structs"
|
||||
"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) (structs.Response, error)
|
||||
}
|
||||
|
||||
func LoadResolver(server string, opts Options) (Resolver, error) {
|
||||
if opts.HTTPS {
|
||||
func LoadResolver(server string, opts cli.Options) (Resolver, error) {
|
||||
switch {
|
||||
case opts.HTTPS:
|
||||
opts.Logger.Debug("loading DoH resolver")
|
||||
if !strings.HasPrefix(server, "https://") {
|
||||
server = "https://" + server
|
||||
|
@ -63,14 +27,19 @@ func LoadResolver(server string, opts Options) (Resolver, error) {
|
|||
server: server,
|
||||
opts: opts,
|
||||
}, nil
|
||||
} else if opts.QUIC {
|
||||
case opts.QUIC:
|
||||
opts.Logger.Debug("loading DoQ resolver")
|
||||
server = net.JoinHostPort(opts.Answers.Server, strconv.Itoa(opts.Port))
|
||||
server = net.JoinHostPort(opts.Request.Server, strconv.Itoa(opts.Port))
|
||||
return &QUICResolver{
|
||||
server: server,
|
||||
opts: opts,
|
||||
}, nil
|
||||
default:
|
||||
opts.Logger.Debug("loading standard/DoT resolver")
|
||||
server = net.JoinHostPort(opts.Request.Server, strconv.Itoa(opts.Port))
|
||||
return &StandardResolver{
|
||||
server: server,
|
||||
opts: opts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -15,18 +15,21 @@ var (
|
|||
)
|
||||
|
||||
func TestIPv4(t *testing.T) {
|
||||
t.Parallel()
|
||||
act, err := ReverseDNS("8.8.4.4", PTR)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, act, "4.4.8.8.in-addr.arpa.", "IPv4 reverse")
|
||||
}
|
||||
|
||||
func TestIPv6(t *testing.T) {
|
||||
t.Parallel()
|
||||
act, err := ReverseDNS("2606:4700:4700::1111", PTR)
|
||||
assert.Nil(t, err)
|
||||
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
|
||||
|
@ -44,6 +47,7 @@ func TestNAPTR(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestInvalid(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := ReverseDNS("AAAAA", 1)
|
||||
assert.NotNil(t, err)
|
||||
assert.Error(t, err)
|
||||
}
|
Loading…
Reference in a new issue