Minor (complete) refactor #38

Merged
sam merged 20 commits from small-things into master 2022-07-26 00:32:32 +00:00
35 changed files with 1363 additions and 856 deletions
Showing only changes of commit 1b5d5a3fed - Show all commits

View file

@ -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
View file

@ -15,4 +15,5 @@
# vendor/
# Go workspace file
go.work
go.work
dist/

39
.goreleaser.yaml Normal file
View 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:'

View file

@ -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

View file

@ -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
View file

@ -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
View file

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
}

View file

@ -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)
})
}

View file

@ -9,6 +9,7 @@ import (
)
func TestGetPlan9Config(t *testing.T) {
t.Parallel()
ndbs := []struct {
in string
want string

168
doc/awl.1 Normal file
View 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
View 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
View file

@ -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
View file

@ -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
View 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
}

View file

@ -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')

View file

@ -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
View 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
View file

@ -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
}

View file

@ -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
View 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{})
}

View file

@ -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
View 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
View 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
View 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)
}

View file

@ -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)
}
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}