Another "minor refactor" (#61)
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
I need to make fewer of these :) Reviewed-on: #61
This commit is contained in:
parent
c70ae88a38
commit
4cf19ebf78
66 changed files with 1569 additions and 1223 deletions
|
@ -26,7 +26,17 @@ local testing(version, arch) = {
|
|||
name: "test",
|
||||
image: "golang:" + version,
|
||||
commands: [
|
||||
"go test -v -race ./... -cover"
|
||||
"make test-ci"
|
||||
],
|
||||
depends_on: [
|
||||
"submodules",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "fuzz",
|
||||
image: "golang:" + version,
|
||||
commands: [
|
||||
"make fuzz",
|
||||
],
|
||||
depends_on: [
|
||||
"submodules",
|
||||
|
@ -65,7 +75,7 @@ local release() = {
|
|||
name: "test",
|
||||
image: "golang",
|
||||
commands: [
|
||||
"go test -race ./... -cover"
|
||||
"make test"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -89,6 +99,5 @@ local release() = {
|
|||
testing("1.18", "amd64"),
|
||||
testing("1.18", "arm64"),
|
||||
|
||||
|
||||
release()
|
||||
]
|
8
.github/ISSUE_TEMPLATE/bug.md
vendored
8
.github/ISSUE_TEMPLATE/bug.md
vendored
|
@ -1,7 +1,6 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Report a bug
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
|
@ -9,6 +8,7 @@ A clear and concise description of what the bug is.
|
|||
|
||||
**Reproduction steps**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. 1
|
||||
2. 2
|
||||
3. Bug
|
||||
|
@ -20,9 +20,9 @@ A clear and concise description of what you expected to happen.
|
|||
Add `-v=4` and add the debug logs to the report here:
|
||||
|
||||
**System information (please complete the following information):**
|
||||
- OS: [e.g. Ubuntu 22.04, OpenBSD, Windows 11]
|
||||
- Version: [run `awl -V` and print the output]
|
||||
|
||||
- OS: [e.g. Ubuntu 22.04, OpenBSD, Windows 11]
|
||||
- Version: [run `awl -V` and print the output]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
Add any other context about the problem here.
|
||||
|
|
3
.github/ISSUE_TEMPLATE/feature.md
vendored
3
.github/ISSUE_TEMPLATE/feature.md
vendored
|
@ -1,7 +1,6 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest a feature
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
|
@ -15,4 +14,4 @@ A clear and concise description of any alternative solutions or features you've
|
|||
Links to implementations in dig, drill, etc. should go here.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
|
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
|
@ -6,4 +6,4 @@ Please check the following:
|
|||
2. Make sure that you test and format your contributions: `make test && make lint`
|
||||
3. Describe what your pull request does and which issue you're targeting (if any)
|
||||
|
||||
-->
|
||||
-->
|
||||
|
|
27
.github/workflows/ghrelease.yaml
vendored
Normal file
27
.github/workflows/ghrelease.yaml
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
name: github-release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout submodules
|
||||
uses: git submodule update --init --recursive
|
||||
|
||||
- name: Release with GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v3
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
24
.github/workflows/test.yaml
vendored
Normal file
24
.github/workflows/test.yaml
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
on: push
|
||||
|
||||
jobs:
|
||||
test-gha:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
platform: [macos-latest, windows-latest]
|
||||
goVer: [1.18 1.19]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ matrix.goVer }}
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Checkout submodules
|
||||
uses: git submodule update --init --recursive
|
||||
|
||||
- name: Test
|
||||
run: go test -race -v ./...
|
26
.github/workflows/wiki.yaml
vendored
Normal file
26
.github/workflows/wiki.yaml
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
name: Deploy Wiki
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
# Trigger only when wiki directory changes
|
||||
- "doc/wiki/**"
|
||||
|
||||
jobs:
|
||||
deploy-wiki:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Checkout submodules
|
||||
uses: git submodule update --init --recursive
|
||||
|
||||
- name: Push Wiki Changes
|
||||
uses: Andrew-Chen-Wang/github-wiki-action@v3
|
||||
env:
|
||||
# Make sure you have that / at the end. We use rsync
|
||||
# WIKI_DIR's default is wiki/
|
||||
WIKI_DIR: doc/wiki/
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_MAIL: ${{ secrets.YOUR_EMAIL }}
|
||||
GH_NAME: ${{ github.repository_owner }}
|
53
.gitignore
vendored
53
.gitignore
vendored
|
@ -1,27 +1,28 @@
|
|||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
dist/
|
||||
|
||||
# Test coverage
|
||||
coverage/*
|
||||
!coverage/.gitkeep
|
||||
|
||||
awl
|
||||
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
dist/
|
||||
|
||||
# Test coverage
|
||||
coverage/*
|
||||
!coverage/.gitkeep
|
||||
|
||||
awl
|
||||
doc/awl.1
|
||||
|
||||
.dccache
|
77
.golangci.yaml
Normal file
77
.golangci.yaml
Normal file
|
@ -0,0 +1,77 @@
|
|||
# Refer to golangci-lint's example config file for more options and information:
|
||||
# https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml
|
||||
|
||||
run:
|
||||
timeout: 5m
|
||||
modules-download-mode: readonly
|
||||
skip-dirs:
|
||||
- "coverage"
|
||||
- ".github"
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- errcheck
|
||||
- errorlint
|
||||
- gci
|
||||
- gocritic
|
||||
- goconst
|
||||
- godot
|
||||
- goimports
|
||||
- govet
|
||||
- gocritic
|
||||
- goerr113
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- gosec
|
||||
- maintidx
|
||||
- makezero
|
||||
- misspell
|
||||
- nlreturn
|
||||
- nolintlint
|
||||
- prealloc
|
||||
- predeclared
|
||||
- revive
|
||||
- staticcheck
|
||||
# - testpackage
|
||||
- whitespace
|
||||
- wrapcheck
|
||||
- wsl
|
||||
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
enable-all: true
|
||||
disable-all: false
|
||||
revive:
|
||||
ignore-generated-header: false
|
||||
severity: warning
|
||||
confidence: 0.8
|
||||
errorCode: 1
|
||||
warningCode: 1
|
||||
rules:
|
||||
# rules can be uncommented after https://github.com/golangci/golangci-lint/issues/2997 is fixed
|
||||
- name: blank-imports
|
||||
- name: context-as-argument
|
||||
# - name: context-keys-type
|
||||
- name: dot-imports
|
||||
- name: duplicated-imports
|
||||
- name: error-return
|
||||
- name: error-strings
|
||||
- name: error-naming
|
||||
# - name: errorf
|
||||
- name: exported
|
||||
- name: if-return
|
||||
- name: increment-decrement
|
||||
# - name: modifies-value-receiver
|
||||
- name: package-comments
|
||||
- name: range
|
||||
- name: receiver-naming
|
||||
# - name: time-naming
|
||||
# - name: unexported-return
|
||||
# - name: var-declaration
|
||||
- name: var-naming
|
||||
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
|
@ -17,7 +17,7 @@ builds:
|
|||
- amd64
|
||||
- arm64
|
||||
universal_binaries:
|
||||
- replace: true
|
||||
- replace: true
|
||||
archives:
|
||||
- replacements:
|
||||
darwin: macOS
|
||||
|
@ -28,14 +28,14 @@ archives:
|
|||
- goos: windows
|
||||
format: zip
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
name_template: "checksums.txt"
|
||||
snapshot:
|
||||
name_template: "{{ incpatch .Version }}-next"
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
- "^docs:"
|
||||
- "^test:"
|
||||
gitea_urls:
|
||||
api: https://git.froth.zone/api/v1/
|
||||
api: https://git.froth.zone/api/v1/
|
||||
|
|
|
@ -6,10 +6,10 @@ ifeq ($(OS),Windows_NT)
|
|||
EXE := $(PROG).exe
|
||||
else
|
||||
EXE := $(PROG)
|
||||
|
||||
endif
|
||||
|
||||
|
||||
$(PROG):
|
||||
$(PROG): $(SOURCES)
|
||||
$(GO) build -o $(EXE) $(GOFLAGS) .
|
||||
|
||||
|
||||
|
@ -19,8 +19,8 @@ install:
|
|||
$(GO) install $(GOFLAGS) .
|
||||
else
|
||||
install: all
|
||||
install -m755 $(PROG) $(PREFIX)/$(BIN)
|
||||
install -m644 doc/$(PROG).1 $(MAN)/man1
|
||||
install -Dm755 $(PROG) $(DESTDIR)$(PREFIX)/$(BIN)/$(PROG)
|
||||
install -Dm644 doc/$(PROG).1 $(DESTDIR)$(MAN)/man1/$(PROG).1
|
||||
endif
|
||||
|
||||
.PHONY: install
|
6
Makefile
6
Makefile
|
@ -3,12 +3,12 @@
|
|||
|
||||
include template.mk
|
||||
|
||||
$(PROG):
|
||||
$(PROG): $(SOURCES)
|
||||
$(GO) build -o $(PROG) $(GOFLAGS) .
|
||||
|
||||
## install: installs awl and the manpage, RUN AS ROOT
|
||||
install: all
|
||||
install -m755 $(PROG) $(PREFIX)/$(BIN)
|
||||
install -m644 doc/$(PROG).1 $(MAN)/man1
|
||||
install -Dm755 $(PROG) $(DESTDIR)$(PREFIX)/$(BIN)/$(PROG)
|
||||
install -Dm644 doc/$(PROG).1 $(DESTDIR)$(MAN)/man1/$(PROG).1
|
||||
|
||||
.PHONY: install
|
|
@ -17,7 +17,6 @@ newer RFC query types, such as DNS-over-HTTPS and DNS-over-QUIC.
|
|||
- [Feature wiki](https://git.froth.zone/sam/awl/wiki/Supported)
|
||||
- [Manpage](https://git.froth.zone/sam/awl/wiki/awl.1)
|
||||
|
||||
|
||||
## Building and installing
|
||||
|
||||
### From releases
|
||||
|
@ -31,7 +30,6 @@ Dependencies:
|
|||
|
||||
- Go >= 1.18
|
||||
- GNU/BSD make or Plan 9 mk
|
||||
- [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (optional, for manpage)
|
||||
|
||||
Make sure to recursively clone the repo:
|
||||
|
||||
|
|
117
cli/cli.go
117
cli/cli.go
|
@ -3,6 +3,7 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
@ -10,7 +11,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
flag "github.com/stefansundin/go-zflag"
|
||||
|
@ -18,7 +18,7 @@ import (
|
|||
|
||||
// ParseCLI parses arguments given from the CLI and passes them into an `Options`
|
||||
// struct.
|
||||
func ParseCLI(version string) (Options, error) {
|
||||
func ParseCLI(version string) (util.Options, error) {
|
||||
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
|
||||
|
||||
flag.Usage = func() {
|
||||
|
@ -57,7 +57,7 @@ func ParseCLI(version string) (Options, error) {
|
|||
cookie = flag.Bool("no-cookie", false, "disable sending EDNS cookie (default: cookie sent)")
|
||||
tcpKeepAlive = flag.Bool("keep-alive", false, "send EDNS TCP keep-alive")
|
||||
udpBufSize = flag.Uint16("buffer-size", 1232, "set EDNS UDP buffer size", flag.OptShorthand('b'))
|
||||
zflag = flag.String("zflag", "0", "set EDNS z-flag")
|
||||
mbzflag = flag.String("zflag", "0", "set EDNS z-flag")
|
||||
subnet = flag.String("subnet", "", "set EDNS subnet")
|
||||
padding = flag.Bool("pad", false, "set EDNS padding")
|
||||
|
||||
|
@ -69,14 +69,17 @@ func ParseCLI(version string) (Options, error) {
|
|||
https = flag.Bool("https", false, "use DNS-over-HTTPS", flag.OptShorthand('H'))
|
||||
quic = flag.Bool("quic", false, "use DNS-over-QUIC", flag.OptShorthand('Q'))
|
||||
|
||||
aa = flag.Bool("aa", false, "set/unset AA (Authoratative Answer) flag (default: not set)")
|
||||
ad = flag.Bool("ad", false, "set/unset AD (Authenticated Data) flag (default: not set)")
|
||||
cd = flag.Bool("cd", false, "set/unset CD (Checking Disabled) flag (default: not set)")
|
||||
qr = flag.Bool("qr", false, "set/unset QR (QueRy) flag (default: not set)")
|
||||
rd = flag.Bool("rd", true, "set/unset RD (Recursion Desired) flag (default: set)", flag.OptDisablePrintDefault(true))
|
||||
ra = flag.Bool("ra", false, "set/unset RA (Recursion Available) flag (default: not set)")
|
||||
tc = flag.Bool("tc", false, "set/unset TC (TrunCated) flag (default: not set)")
|
||||
z = flag.Bool("z", false, "set/unset Z (Zero) flag (default: not set)", flag.OptShorthand('z'))
|
||||
tlsHost = flag.String("tls-host", "", "Server name to use for TLS verification")
|
||||
noVerify = flag.Bool("tls-no-verify", false, "Disable TLS cert verification")
|
||||
|
||||
aaflag = flag.Bool("aa", false, "set/unset AA (Authoratative Answer) flag (default: not set)")
|
||||
adflag = flag.Bool("ad", false, "set/unset AD (Authenticated Data) flag (default: not set)")
|
||||
cdflag = flag.Bool("cd", false, "set/unset CD (Checking Disabled) flag (default: not set)")
|
||||
qrflag = flag.Bool("qr", false, "set/unset QR (QueRy) flag (default: not set)")
|
||||
rdflag = flag.Bool("rd", true, "set/unset RD (Recursion Desired) flag (default: set)", flag.OptDisablePrintDefault(true))
|
||||
raflag = flag.Bool("ra", false, "set/unset RA (Recursion Available) flag (default: not set)")
|
||||
tcflag = flag.Bool("tc", false, "set/unset TC (TrunCated) flag (default: not set)")
|
||||
zflag = flag.Bool("z", false, "set/unset Z (Zero) flag (default: not set)", flag.OptShorthand('z'))
|
||||
|
||||
short = flag.Bool("short", false, "print just the results", flag.OptShorthand('s'))
|
||||
json = flag.Bool("json", false, "print the result(s) as JSON", flag.OptShorthand('j'))
|
||||
|
@ -99,52 +102,53 @@ func ParseCLI(version string) (Options, error) {
|
|||
flag.CommandLine.SortFlags = false
|
||||
|
||||
// Parse the flags
|
||||
err := flag.CommandLine.Parse(os.Args[1:])
|
||||
if err != nil {
|
||||
return Options{Logger: util.InitLogger(*verbosity)}, fmt.Errorf("flag parse error: %w", err)
|
||||
if err := flag.CommandLine.Parse(os.Args[1:]); err != nil {
|
||||
return util.Options{Logger: util.InitLogger(*verbosity)}, fmt.Errorf("flag: %w", err)
|
||||
}
|
||||
|
||||
// TODO: DRY, dumb dumb.
|
||||
mbz, err := strconv.ParseInt(*zflag, 0, 16)
|
||||
mbz, err := strconv.ParseInt(*mbzflag, 0, 16)
|
||||
if err != nil {
|
||||
return Options{Logger: util.InitLogger(*verbosity)}, fmt.Errorf("EDNS MBZ error: %w", err)
|
||||
return util.Options{Logger: util.InitLogger(*verbosity)}, fmt.Errorf("EDNS MBZ: %w", err)
|
||||
}
|
||||
|
||||
opts := Options{
|
||||
Logger: util.InitLogger(*verbosity),
|
||||
Port: *port,
|
||||
IPv4: *ipv4,
|
||||
IPv6: *ipv6,
|
||||
Short: *short,
|
||||
TCP: *tcp,
|
||||
DNSCrypt: *dnscrypt,
|
||||
TLS: *tls,
|
||||
HTTPS: *https,
|
||||
QUIC: *quic,
|
||||
Truncate: *truncate,
|
||||
ShowQuery: false,
|
||||
AA: *aa,
|
||||
AD: *ad,
|
||||
TC: *tc,
|
||||
Z: *z,
|
||||
CD: *cd,
|
||||
QR: *qr,
|
||||
RD: *rd,
|
||||
RA: *ra,
|
||||
Reverse: *reverse,
|
||||
HumanTTL: false,
|
||||
ShowTTL: true,
|
||||
JSON: *json,
|
||||
XML: *xml,
|
||||
YAML: *yaml,
|
||||
Request: helpers.Request{
|
||||
opts := util.Options{
|
||||
Logger: util.InitLogger(*verbosity),
|
||||
Port: *port,
|
||||
IPv4: *ipv4,
|
||||
IPv6: *ipv6,
|
||||
Short: *short,
|
||||
TCP: *tcp,
|
||||
DNSCrypt: *dnscrypt,
|
||||
TLS: *tls,
|
||||
TLSHost: *tlsHost,
|
||||
TLSNoVerify: *noVerify,
|
||||
HTTPS: *https,
|
||||
QUIC: *quic,
|
||||
Truncate: *truncate,
|
||||
ShowQuery: false,
|
||||
AA: *aaflag,
|
||||
AD: *adflag,
|
||||
TC: *tcflag,
|
||||
Z: *zflag,
|
||||
CD: *cdflag,
|
||||
QR: *qrflag,
|
||||
RD: *rdflag,
|
||||
RA: *raflag,
|
||||
Reverse: *reverse,
|
||||
HumanTTL: false,
|
||||
ShowTTL: true,
|
||||
JSON: *json,
|
||||
XML: *xml,
|
||||
YAML: *yaml,
|
||||
Request: util.Request{
|
||||
Type: dns.StringToType[strings.ToUpper(*qType)],
|
||||
Class: dns.StringToClass[strings.ToUpper(*class)],
|
||||
Name: *query,
|
||||
Timeout: time.Duration(*timeout * float32(time.Second)),
|
||||
Retries: *retry,
|
||||
},
|
||||
Display: Displays{
|
||||
Display: util.Displays{
|
||||
Comments: !*noC,
|
||||
Question: !*noQ,
|
||||
Opt: !*noOpt,
|
||||
|
@ -153,7 +157,7 @@ func ParseCLI(version string) (Options, error) {
|
|||
Additional: !*noAdd,
|
||||
Statistics: !*noStats,
|
||||
},
|
||||
EDNS: EDNS{
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: !*edns,
|
||||
Cookie: !*cookie,
|
||||
DNSSEC: *dnssec,
|
||||
|
@ -169,9 +173,8 @@ func ParseCLI(version string) (Options, error) {
|
|||
|
||||
// TODO: DRY
|
||||
if *subnet != "" {
|
||||
err := parseSubnet(*subnet, &opts)
|
||||
if err != nil {
|
||||
return opts, err
|
||||
if err = util.ParseSubnet(*subnet, &opts); err != nil {
|
||||
return opts, fmt.Errorf("%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,6 +183,7 @@ func ParseCLI(version string) (Options, error) {
|
|||
|
||||
if *versionFlag {
|
||||
fmt.Printf("awl version %s, built with %s\n", version, runtime.Version())
|
||||
|
||||
return opts, ErrNotError
|
||||
}
|
||||
|
||||
|
@ -189,6 +193,7 @@ func ParseCLI(version string) (Options, error) {
|
|||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
|
||||
opts.Logger.Info("Dig/Drill flags parsed")
|
||||
opts.Logger.Debug(fmt.Sprintf("%+v", opts))
|
||||
|
||||
|
@ -199,6 +204,7 @@ func ParseCLI(version string) (Options, error) {
|
|||
opts.Port = 53
|
||||
}
|
||||
}
|
||||
|
||||
opts.Logger.Info("Port set to", opts.Port)
|
||||
|
||||
// Set timeout to 0.5 seconds if set below 0.5
|
||||
|
@ -212,3 +218,16 @@ func ParseCLI(version string) (Options, error) {
|
|||
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
// ErrNotError is for returning not error.
|
||||
var ErrNotError = errors.New("not an error")
|
||||
|
||||
var errNoArg = errors.New("no argument given")
|
||||
|
||||
type errInvalidArg struct {
|
||||
arg string
|
||||
}
|
||||
|
||||
func (e *errInvalidArg) Error() string {
|
||||
return fmt.Sprintf("digflags: invalid argument %s", e.arg)
|
||||
}
|
||||
|
|
|
@ -11,77 +11,111 @@ import (
|
|||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
// nolint: paralleltest
|
||||
func TestEmpty(t *testing.T) {
|
||||
old := os.Args
|
||||
os.Args = []string{"awl", "-4"}
|
||||
|
||||
opts, err := cli.ParseCLI("TEST")
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.Port, 53)
|
||||
assert.Assert(t, opts.IPv4)
|
||||
|
||||
os.Args = old
|
||||
}
|
||||
|
||||
// nolint: paralleltest
|
||||
func TestTLSPort(t *testing.T) {
|
||||
old := os.Args
|
||||
os.Args = []string{"awl", "-T"}
|
||||
|
||||
opts, err := cli.ParseCLI("TEST")
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.Port, 853)
|
||||
|
||||
os.Args = old
|
||||
}
|
||||
|
||||
// nolint: paralleltest
|
||||
func TestSubnet(t *testing.T) {
|
||||
old := os.Args
|
||||
os.Args = []string{"awl", "--subnet", "127.0.0.1/32"}
|
||||
|
||||
opts, err := cli.ParseCLI("TEST")
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.EDNS.Subnet.Family, uint16(1))
|
||||
|
||||
os.Args = old
|
||||
|
||||
os.Args = []string{"awl", "--subnet", "0"}
|
||||
|
||||
opts, err = cli.ParseCLI("TEST")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.EDNS.Subnet.Family, uint16(1))
|
||||
|
||||
os.Args = old
|
||||
|
||||
os.Args = []string{"awl", "--subnet", "::/0"}
|
||||
|
||||
opts, err = cli.ParseCLI("TEST")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opts.EDNS.Subnet.Family, uint16(2))
|
||||
|
||||
os.Args = old
|
||||
|
||||
os.Args = []string{"awl", "--subnet", "/"}
|
||||
|
||||
opts, err = cli.ParseCLI("TEST")
|
||||
assert.ErrorContains(t, err, "EDNS subnet")
|
||||
|
||||
os.Args = old
|
||||
}
|
||||
|
||||
// nolint: paralleltest
|
||||
func TestInvalidFlag(t *testing.T) {
|
||||
func TestMBZ(t *testing.T) { //nolint: paralleltest // Race conditions
|
||||
old := os.Args
|
||||
os.Args = []string{"awl", "--zflag", "G"}
|
||||
|
||||
_, err := cli.ParseCLI("TEST")
|
||||
|
||||
assert.ErrorContains(t, err, "EDNS MBZ")
|
||||
|
||||
os.Args = old
|
||||
}
|
||||
|
||||
func TestInvalidFlag(t *testing.T) { //nolint: paralleltest // Race conditions
|
||||
old := os.Args
|
||||
os.Args = []string{"awl", "--treebug"}
|
||||
|
||||
_, err := cli.ParseCLI("TEST")
|
||||
|
||||
assert.ErrorContains(t, err, "unknown flag")
|
||||
|
||||
os.Args = old
|
||||
}
|
||||
|
||||
// nolint: paralleltest
|
||||
func TestInvalidDig(t *testing.T) {
|
||||
func TestInvalidDig(t *testing.T) { //nolint: paralleltest // Race conditions
|
||||
old := os.Args
|
||||
os.Args = []string{"awl", "+a"}
|
||||
|
||||
_, err := cli.ParseCLI("TEST")
|
||||
|
||||
assert.ErrorContains(t, err, "digflags: invalid argument")
|
||||
|
||||
os.Args = old
|
||||
}
|
||||
|
||||
// nolint: paralleltest
|
||||
func TestVersion(t *testing.T) {
|
||||
func TestVersion(t *testing.T) { //nolint: paralleltest // Race conditions
|
||||
old := os.Args
|
||||
os.Args = []string{"awl", "--version"}
|
||||
|
||||
_, err := cli.ParseCLI("TEST")
|
||||
|
||||
assert.ErrorType(t, err, cli.ErrNotError)
|
||||
|
||||
os.Args = old
|
||||
}
|
||||
|
||||
// nolint: paralleltest
|
||||
func TestTimeout(t *testing.T) {
|
||||
func TestTimeout(t *testing.T) { //nolint: paralleltest // Race conditions
|
||||
args := [][]string{
|
||||
{"awl", "+timeout=0"},
|
||||
{"awl", "--timeout", "0"},
|
||||
|
@ -89,15 +123,17 @@ func TestTimeout(t *testing.T) {
|
|||
for _, test := range args {
|
||||
old := os.Args
|
||||
os.Args = test
|
||||
|
||||
opt, err := cli.ParseCLI("TEST")
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opt.Request.Timeout, time.Second/2)
|
||||
|
||||
os.Args = old
|
||||
}
|
||||
}
|
||||
|
||||
// nolint: paralleltest
|
||||
func TestRetries(t *testing.T) {
|
||||
func TestRetries(t *testing.T) { //nolint: paralleltest // Race conditions
|
||||
args := [][]string{
|
||||
{"awl", "+retry=-2"},
|
||||
{"awl", "+tries=-2"},
|
||||
|
@ -106,14 +142,16 @@ func TestRetries(t *testing.T) {
|
|||
for _, test := range args {
|
||||
old := os.Args
|
||||
os.Args = test
|
||||
|
||||
opt, err := cli.ParseCLI("TEST")
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, opt.Request.Retries, 0)
|
||||
|
||||
os.Args = old
|
||||
}
|
||||
}
|
||||
|
||||
// nolint: paralleltest
|
||||
func FuzzFlags(f *testing.F) {
|
||||
testcases := []string{"git.froth.zone", "", "!12345", "google.com.edu.org.fr"}
|
||||
for _, tc := range testcases {
|
||||
|
@ -121,8 +159,10 @@ func FuzzFlags(f *testing.F) {
|
|||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, orig string) {
|
||||
old := os.Args
|
||||
os.Args = []string{"awl", orig}
|
||||
//nolint:errcheck // Only make sure the program does not crash
|
||||
//nolint:errcheck,gosec // Only make sure the program does not crash
|
||||
cli.ParseCLI("TEST")
|
||||
os.Args = old
|
||||
})
|
||||
}
|
||||
|
|
176
cli/dig.go
176
cli/dig.go
|
@ -7,18 +7,21 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/util"
|
||||
)
|
||||
|
||||
// ParseDig parses commands from the popular DNS tool dig.
|
||||
// All dig commands are taken from https://man.openbsd.org/dig.1 as the source of their functionality.
|
||||
//
|
||||
// [no]flags are supported just as flag are and are disabled as such.
|
||||
func ParseDig(arg string, opts *Options) error {
|
||||
func ParseDig(arg string, opts *util.Options) error {
|
||||
// returns true if the flag starts with a no
|
||||
isNo := !strings.HasPrefix(arg, "no")
|
||||
if !isNo {
|
||||
arg = strings.TrimPrefix(arg, "no")
|
||||
}
|
||||
|
||||
opts.Logger.Info("Setting", arg)
|
||||
|
||||
switch arg {
|
||||
|
@ -118,85 +121,100 @@ func ParseDig(arg string, opts *Options) error {
|
|||
opts.Display.UcodeTranslate = isNo
|
||||
|
||||
default:
|
||||
// Recursive switch statements WOO
|
||||
arg := strings.Split(arg, "=")
|
||||
switch arg[0] {
|
||||
case "time", "timeout":
|
||||
if len(arg) > 1 && arg[1] != "" {
|
||||
timeout, err := strconv.Atoi(arg[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("digflags: Invalid timeout value: %w", err)
|
||||
}
|
||||
|
||||
opts.Request.Timeout = time.Duration(timeout)
|
||||
} else {
|
||||
return fmt.Errorf("digflags: Invalid timeout value: %w", errNoArg)
|
||||
}
|
||||
|
||||
case "retry", "tries":
|
||||
if len(arg) > 1 && arg[1] != "" {
|
||||
tries, err := strconv.Atoi(arg[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("digflags: Invalid retry value: %w", err)
|
||||
}
|
||||
opts.Request.Retries = tries
|
||||
|
||||
// TODO: Is there a better way to do this?
|
||||
if arg[0] == "tries" {
|
||||
opts.Request.Retries++
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("digflags: Invalid retry value: %w", errNoArg)
|
||||
}
|
||||
|
||||
case "bufsize":
|
||||
if len(arg) > 1 && arg[1] != "" {
|
||||
size, err := strconv.Atoi(arg[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("digflags: Invalid UDP buffer size value: %w", err)
|
||||
}
|
||||
opts.EDNS.BufSize = uint16(size)
|
||||
} else {
|
||||
return fmt.Errorf("digflags: Invalid UDP buffer size value: %w", errNoArg)
|
||||
}
|
||||
|
||||
case "ednsflags":
|
||||
if len(arg) > 1 && arg[1] != "" {
|
||||
ver, err := strconv.ParseInt(arg[1], 0, 16)
|
||||
if err != nil {
|
||||
return fmt.Errorf("digflags: Invalid EDNS flag: %w", err)
|
||||
}
|
||||
// Ignore setting DO bit
|
||||
opts.EDNS.ZFlag = uint16(ver & 0x7FFF)
|
||||
} else {
|
||||
opts.EDNS.ZFlag = 0
|
||||
}
|
||||
|
||||
case "edns":
|
||||
opts.EDNS.EnableEDNS = isNo
|
||||
if len(arg) > 1 && arg[1] != "" {
|
||||
ver, err := strconv.Atoi(arg[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("digflags: Invalid EDNS version: %w", err)
|
||||
}
|
||||
opts.EDNS.Version = uint8(ver)
|
||||
} else {
|
||||
opts.EDNS.Version = 0
|
||||
}
|
||||
|
||||
case "subnet":
|
||||
if len(arg) > 1 && arg[1] != "" {
|
||||
err := parseSubnet(arg[1], opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("digflags: Invalid EDNS Subnet: %w", err)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("digflags: Invalid EDNS Subnet: %w", errNoArg)
|
||||
}
|
||||
|
||||
default:
|
||||
return &errInvalidArg{arg[0]}
|
||||
if err := parseDigEq(isNo, arg, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// For flags that contain "=".
|
||||
func parseDigEq(startNo bool, arg string, opts *util.Options) error {
|
||||
// Recursive switch statements WOO
|
||||
arg, val, isSplit := strings.Cut(arg, "=")
|
||||
switch arg {
|
||||
case "time", "timeout":
|
||||
if isSplit && val != "" {
|
||||
timeout, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("digflags: timeout : %w", err)
|
||||
}
|
||||
|
||||
opts.Request.Timeout = time.Duration(timeout)
|
||||
} else {
|
||||
return fmt.Errorf("digflags: timeout: %w", errNoArg)
|
||||
}
|
||||
|
||||
case "retry", "tries":
|
||||
if isSplit && val != "" {
|
||||
tries, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("digflags: retry: %w", err)
|
||||
}
|
||||
|
||||
opts.Request.Retries = tries
|
||||
|
||||
// TODO: Is there a better way to do this?
|
||||
if arg == "tries" {
|
||||
opts.Request.Retries++
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("digflags: retry: %w", errNoArg)
|
||||
}
|
||||
|
||||
case "bufsize":
|
||||
if isSplit && val != "" {
|
||||
size, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("digflags: EDNS UDP: %w", err)
|
||||
}
|
||||
|
||||
opts.EDNS.BufSize = uint16(size)
|
||||
} else {
|
||||
return fmt.Errorf("digflags: EDNS UDP: %w", errNoArg)
|
||||
}
|
||||
|
||||
case "ednsflags":
|
||||
if isSplit && val != "" {
|
||||
ver, err := strconv.ParseInt(val, 0, 16)
|
||||
if err != nil {
|
||||
return fmt.Errorf("digflags: EDNS flag: %w", err)
|
||||
}
|
||||
|
||||
// Ignore setting DO bit
|
||||
opts.EDNS.ZFlag = uint16(ver & 0x7FFF)
|
||||
} else {
|
||||
opts.EDNS.ZFlag = 0
|
||||
}
|
||||
|
||||
case "edns":
|
||||
opts.EDNS.EnableEDNS = startNo
|
||||
|
||||
if isSplit && val != "" {
|
||||
ver, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("digflags: EDNS version: %w", err)
|
||||
}
|
||||
|
||||
opts.EDNS.Version = uint8(ver)
|
||||
} else {
|
||||
opts.EDNS.Version = 0
|
||||
}
|
||||
|
||||
case "subnet":
|
||||
if isSplit && val != "" {
|
||||
err := util.ParseSubnet(val, opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("digflags: EDNS Subnet: %w", err)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("digflags: EDNS Subnet: %w", errNoArg)
|
||||
}
|
||||
|
||||
default:
|
||||
return &errInvalidArg{arg}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
func FuzzDig(f *testing.F) {
|
||||
f.Log("ParseDig Fuzzing")
|
||||
|
||||
seeds := []string{
|
||||
"aaflag", "aaonly", "noaaflag", "noaaonly",
|
||||
"adflag", "noadflag",
|
||||
|
@ -59,15 +60,15 @@ func FuzzDig(f *testing.F) {
|
|||
"idnout", "noidnout",
|
||||
"invalid",
|
||||
}
|
||||
|
||||
for _, tc := range seeds {
|
||||
f.Add(tc)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, orig string) {
|
||||
opts := new(cli.Options)
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
err := cli.ParseDig(orig, opts)
|
||||
if err != nil {
|
||||
if err := cli.ParseDig(orig, opts); err != nil {
|
||||
assert.ErrorContains(t, err, "digflags:")
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
The CLI part of the package, including both POSIX
|
||||
Package cli is the CLI part of the package, including both POSIX
|
||||
flag parsing and dig-like flag parsing.
|
||||
*/
|
||||
package cli
|
||||
|
|
25
cli/misc.go
25
cli/misc.go
|
@ -15,17 +15,19 @@ import (
|
|||
|
||||
// ParseMiscArgs parses the wildcard arguments, drill style.
|
||||
// Only one command is supported at a time, so any extra information overrides previous.
|
||||
func ParseMiscArgs(args []string, opts *Options) error {
|
||||
func ParseMiscArgs(args []string, opts *util.Options) error {
|
||||
var err error
|
||||
|
||||
for _, arg := range args {
|
||||
r, ok := dns.StringToType[strings.ToUpper(arg)]
|
||||
|
||||
switch {
|
||||
// If it starts with @, it's a DNS server
|
||||
case strings.HasPrefix(arg, "@"):
|
||||
arg = arg[1:]
|
||||
// Automatically set flags based on URI header
|
||||
opts.Logger.Info(arg, "detected as a server")
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(arg, "tls://"):
|
||||
opts.TLS = true
|
||||
|
@ -51,6 +53,7 @@ func ParseMiscArgs(args []string, opts *Options) error {
|
|||
case strings.HasPrefix(arg, "+"):
|
||||
opts.Logger.Info(arg, "detected as a dig query")
|
||||
err = ParseDig(strings.ToLower(arg[1:]), opts)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -59,8 +62,9 @@ func ParseMiscArgs(args []string, opts *Options) error {
|
|||
case strings.Contains(arg, "."):
|
||||
opts.Logger.Info(arg, "detected as a domain name")
|
||||
opts.Request.Name, err = idna.ToASCII(arg)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("punycode translate error: %w", err)
|
||||
return fmt.Errorf("unicode to punycode: %w", err)
|
||||
}
|
||||
|
||||
// DNS query type
|
||||
|
@ -72,8 +76,9 @@ func ParseMiscArgs(args []string, opts *Options) error {
|
|||
default:
|
||||
opts.Logger.Info(arg, "is unknown. Assuming domain")
|
||||
opts.Request.Name, err = idna.ToASCII(arg)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("punycode translate error: %w", err)
|
||||
return fmt.Errorf("unicode to punycode: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,11 +87,13 @@ func ParseMiscArgs(args []string, opts *Options) error {
|
|||
if opts.Request.Name == "" {
|
||||
opts.Logger.Info("Domain not specified, making a default")
|
||||
opts.Request.Name = "."
|
||||
|
||||
if opts.Request.Type == 0 {
|
||||
opts.Request.Type = dns.StringToType["NS"]
|
||||
}
|
||||
} else {
|
||||
opts.Logger.Info("Query not specified, making an \"A\" query")
|
||||
|
||||
if opts.Request.Type == 0 {
|
||||
opts.Request.Type = dns.StringToType["A"]
|
||||
}
|
||||
|
@ -106,7 +113,9 @@ func ParseMiscArgs(args []string, opts *Options) error {
|
|||
case opts.QUIC:
|
||||
opts.Request.Server = "dns.adguard.com"
|
||||
default:
|
||||
//nolint:govet // This shadow is intentional
|
||||
resolv, err := conf.GetDNSConfig()
|
||||
|
||||
if err != nil {
|
||||
// :^)
|
||||
opts.Logger.Warn("Could not query system for server. Using default")
|
||||
|
@ -119,40 +128,48 @@ func ParseMiscArgs(args []string, opts *Options) error {
|
|||
case opts.IPv4:
|
||||
if strings.Contains(srv, ".") {
|
||||
opts.Request.Server = srv
|
||||
|
||||
break harmful
|
||||
}
|
||||
case opts.IPv6:
|
||||
if strings.Contains(srv, ":") {
|
||||
opts.Request.Server = srv
|
||||
|
||||
break harmful
|
||||
}
|
||||
default:
|
||||
//#nosec -- This isn't used for anything secure
|
||||
opts.Request.Server = resolv.Servers[rand.Intn(len(resolv.Servers))]
|
||||
|
||||
break harmful
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
opts.Logger.Info("DNS server set to", opts.Request.Server)
|
||||
|
||||
// Make reverse adresses proper addresses
|
||||
if opts.Reverse {
|
||||
opts.Logger.Info("Making reverse DNS query proper *.arpa domain")
|
||||
|
||||
if dns.TypeToString[opts.Request.Type] == "A" {
|
||||
opts.Request.Type = dns.StringToType["PTR"]
|
||||
}
|
||||
|
||||
opts.Request.Name, err = util.ReverseDNS(opts.Request.Name, opts.Request.Type)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reverse DNS error: %w", err)
|
||||
return fmt.Errorf("reverse DNS: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// if the domain is not canonical, make it canonical
|
||||
if !strings.HasSuffix(opts.Request.Name, ".") {
|
||||
opts.Request.Name = fmt.Sprintf("%s.", opts.Request.Name)
|
||||
|
||||
opts.Logger.Debug("Domain made canonical")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -14,13 +14,14 @@ import (
|
|||
|
||||
func TestParseArgs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{
|
||||
"go.dev",
|
||||
"AAAA",
|
||||
"@1.1.1.1",
|
||||
"+ignore",
|
||||
}
|
||||
opts := new(cli.Options)
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
err := cli.ParseMiscArgs(args, opts)
|
||||
assert.NilError(t, err)
|
||||
|
@ -32,8 +33,9 @@ func TestParseArgs(t *testing.T) {
|
|||
|
||||
func TestParseNoInput(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{}
|
||||
opts := new(cli.Options)
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
err := cli.ParseMiscArgs(args, opts)
|
||||
assert.NilError(t, err)
|
||||
|
@ -43,10 +45,11 @@ func TestParseNoInput(t *testing.T) {
|
|||
|
||||
func TestParseA(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{
|
||||
"golang.org.",
|
||||
}
|
||||
opts := new(cli.Options)
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
err := cli.ParseMiscArgs(args, opts)
|
||||
assert.NilError(t, err)
|
||||
|
@ -56,8 +59,9 @@ func TestParseA(t *testing.T) {
|
|||
|
||||
func TestParsePTR(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"8.8.8.8"}
|
||||
opts := new(cli.Options)
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
opts.Reverse = true
|
||||
err := cli.ParseMiscArgs(args, opts)
|
||||
|
@ -67,8 +71,9 @@ func TestParsePTR(t *testing.T) {
|
|||
|
||||
func TestParseInvalidPTR(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
args := []string{"8.88.8"}
|
||||
opts := new(cli.Options)
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
opts.Reverse = true
|
||||
err := cli.ParseMiscArgs(args, opts)
|
||||
|
@ -77,6 +82,7 @@ func TestParseInvalidPTR(t *testing.T) {
|
|||
|
||||
func TestDefaultServer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
in string
|
||||
want string
|
||||
|
@ -86,12 +92,13 @@ func TestDefaultServer(t *testing.T) {
|
|||
{"HTTPS", "https://dns.cloudflare.com/dns-query"},
|
||||
{"QUIC", "dns.adguard.com"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.in, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
args := []string{}
|
||||
opts := new(cli.Options)
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
switch test.in {
|
||||
case "DNSCRYPT":
|
||||
|
@ -112,6 +119,7 @@ func TestDefaultServer(t *testing.T) {
|
|||
|
||||
func TestFlagSetting(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
in []string
|
||||
}{
|
||||
|
@ -120,11 +128,12 @@ func TestFlagSetting(t *testing.T) {
|
|||
{[]string{"@https://dns.cloudflare.com/dns-query"}},
|
||||
{[]string{"@quic://dns.adguard.com"}},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
test := test
|
||||
i := i
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
opts := new(cli.Options)
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
t.Parallel()
|
||||
err := cli.ParseMiscArgs(test.in, opts)
|
||||
|
@ -151,14 +160,16 @@ func FuzzParseArgs(f *testing.F) {
|
|||
"+ignore",
|
||||
"e",
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
f.Add(tc)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, arg string) {
|
||||
args := []string{arg}
|
||||
opts := new(cli.Options)
|
||||
opts := new(util.Options)
|
||||
opts.Logger = util.InitLogger(0)
|
||||
//nolint:errcheck // Only make sure the program does not crash
|
||||
//nolint:errcheck,gosec // Only make sure the program does not crash
|
||||
cli.ParseMiscArgs(args, opts)
|
||||
})
|
||||
}
|
||||
|
|
121
cli/options.go
121
cli/options.go
|
@ -1,121 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"git.froth.zone/sam/awl/logawl"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// CLI options structure.
|
||||
type Options struct {
|
||||
Logger *logawl.Logger // Logger
|
||||
Port int // DNS port
|
||||
IPv4 bool // Force IPv4
|
||||
IPv6 bool // Force IPv6
|
||||
TCP bool // Query with TCP
|
||||
DNSCrypt bool // Query over DNSCrypt
|
||||
TLS bool // Query over TLS
|
||||
HTTPS bool // Query over HTTPS
|
||||
QUIC bool // Query over QUIC
|
||||
Truncate bool // Ignore truncation
|
||||
ShowQuery bool // Show query before being sent
|
||||
AA bool // Set Authoratative Answer
|
||||
AD bool // Set Authenticated Data
|
||||
CD bool // Set CD
|
||||
QR bool // Set QueRy
|
||||
RD bool // Set Recursion Desired
|
||||
RA bool // Set Recursion Available
|
||||
TC bool // Set TC (TrunCated)
|
||||
Z bool // Set Z (Zero)
|
||||
Reverse bool // Make reverse query
|
||||
Verbosity int // Set logawl verbosity
|
||||
HumanTTL bool // Make TTL human readable
|
||||
ShowTTL bool // Display TTL
|
||||
Short bool // Short output
|
||||
Identify bool // If short, add identity stuffs
|
||||
JSON bool // Outout as JSON
|
||||
XML bool // Output as XML
|
||||
YAML bool // Output at YAML
|
||||
|
||||
Display Displays // Display options
|
||||
Request helpers.Request // DNS reuqest
|
||||
EDNS // EDNS
|
||||
}
|
||||
|
||||
// What to (and not to) display.
|
||||
type Displays struct {
|
||||
Comments bool
|
||||
Question bool // QUESTION SECTION
|
||||
Opt bool // OPT PSEUDOSECTION
|
||||
Answer bool // ANSWER SECTION
|
||||
Authority bool // AUTHORITY SECTION
|
||||
Additional bool // ADDITIONAL SECTION
|
||||
Statistics bool // Query time, message size, etc.
|
||||
UcodeTranslate bool // Translate Punycode back to Unicode
|
||||
}
|
||||
|
||||
type EDNS struct {
|
||||
EnableEDNS bool // Enable EDNS
|
||||
Cookie bool // Enable EDNS cookie
|
||||
DNSSEC bool // Enable DNSSEC
|
||||
BufSize uint16 // Set UDP buffer size
|
||||
Version uint8 // Set EDNS version
|
||||
Expire bool // Set EDNS expiration
|
||||
KeepOpen bool // TCP keep alive
|
||||
Nsid bool // Show EDNS nsid
|
||||
ZFlag uint16 // EDNS flags
|
||||
Padding bool // EDNS padding
|
||||
Subnet dns.EDNS0_SUBNET // EDNS Subnet (duh)
|
||||
}
|
||||
|
||||
// parseSubnet takes a subnet argument and makes it into one that the DNS library
|
||||
// understands.
|
||||
func parseSubnet(subnet string, opts *Options) error {
|
||||
ip, inet, err := net.ParseCIDR(subnet)
|
||||
if err != nil {
|
||||
// TODO: make not a default?
|
||||
if subnet == "0" {
|
||||
opts.EDNS.Subnet = dns.EDNS0_SUBNET{
|
||||
Code: dns.EDNS0SUBNET,
|
||||
Family: 1,
|
||||
SourceNetmask: 0,
|
||||
SourceScope: 0,
|
||||
Address: net.IPv4(0, 0, 0, 0),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("subnet parsing error %w", err)
|
||||
}
|
||||
sub, _ := inet.Mask.Size()
|
||||
opts.EDNS.Subnet = dns.EDNS0_SUBNET{}
|
||||
opts.EDNS.Subnet.Address = ip
|
||||
opts.EDNS.Subnet.SourceNetmask = uint8(sub)
|
||||
|
||||
switch ip.To4() {
|
||||
case nil:
|
||||
// Not a valid IPv4 so assume IPv6
|
||||
opts.EDNS.Subnet.Family = 2
|
||||
default:
|
||||
// Valid IPv4
|
||||
opts.EDNS.Subnet.Family = 1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var ErrNotError = errors.New("not an error")
|
||||
|
||||
var errNoArg = errors.New("no argument given")
|
||||
|
||||
type errInvalidArg struct {
|
||||
arg string
|
||||
}
|
||||
|
||||
func (e *errInvalidArg) Error() string {
|
||||
return fmt.Sprintf("digflags: invalid argument %s", e.arg)
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Helper functions for getting local nameservers
|
||||
Package conf contains helper functions for getting local nameservers
|
||||
|
||||
Currently supported: Unix, Windows, Plan 9 (tested on 9front)
|
||||
*/
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// GetPlan9Config gets DNS information from Plan 9, because it's different from UNIX and Windows.
|
||||
// Plan 9 stores its network data in /net/ndb, which seems to be formatted a specific way
|
||||
// Yoink it and use it.
|
||||
//
|
||||
|
@ -16,12 +17,15 @@ import (
|
|||
func GetPlan9Config(str string) (*dns.ClientConfig, error) {
|
||||
str = strings.ReplaceAll(str, "\n", "")
|
||||
spl := strings.FieldsFunc(str, splitChars)
|
||||
|
||||
var servers []string
|
||||
|
||||
for _, option := range spl {
|
||||
if strings.HasPrefix(option, "dns=") {
|
||||
servers = append(servers, strings.TrimPrefix(option, "dns="))
|
||||
}
|
||||
}
|
||||
|
||||
if len(servers) == 0 {
|
||||
return nil, errPlan9
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
func TestGetPlan9Config(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ndbs := []struct {
|
||||
in string
|
||||
want string
|
||||
|
|
10
conf/unix.go
10
conf/unix.go
|
@ -11,15 +11,21 @@ import (
|
|||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Get the DNS configuration, either from /etc/resolv.conf or somewhere else
|
||||
// GetDNSConfig gets the DNS configuration, either from /etc/resolv.conf or somewhere else.
|
||||
func GetDNSConfig() (*dns.ClientConfig, error) {
|
||||
if runtime.GOOS == "plan9" {
|
||||
dat, err := os.ReadFile("/net/ndb")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("plan9 ndb: %w", err)
|
||||
}
|
||||
|
||||
return GetPlan9Config(string(dat))
|
||||
} else {
|
||||
return dns.ClientConfigFromFile("/etc/resolv.conf")
|
||||
conf, err := dns.ClientConfigFromFile("/etc/resolv.conf")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unix config: %w", err)
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ func TestNonWinConfig(t *testing.T) {
|
|||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Running Windows, skipping")
|
||||
}
|
||||
|
||||
conf, err := conf.GetDNSConfig()
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, len(conf.Servers) != 0)
|
||||
|
|
21
conf/win.go
21
conf/win.go
|
@ -17,37 +17,48 @@ import (
|
|||
https://gist.github.com/moloch--/9fb1c8497b09b45c840fe93dd23b1e98
|
||||
*/
|
||||
|
||||
// WindowsDnsClientConfig - returns all DNS server addresses using windows fuckery.
|
||||
// GetDNSConfig (Windows version) returns all DNS server addresses using windows fuckery.
|
||||
//
|
||||
// Here be dragons.
|
||||
func GetDNSConfig() (*dns.ClientConfig, error) {
|
||||
l := uint32(20000)
|
||||
b := make([]byte, l)
|
||||
length := uint32(20000)
|
||||
byt := make([]byte, length)
|
||||
|
||||
// Windows is an utter fucking trash fire of an operating system.
|
||||
if err := windows.GetAdaptersAddresses(windows.AF_UNSPEC, windows.GAA_FLAG_INCLUDE_PREFIX, 0, (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])), &l); err != nil {
|
||||
//nolint:gosec // This is necessary unless we want to drop 1.18
|
||||
if err := windows.GetAdaptersAddresses(windows.AF_UNSPEC, windows.GAA_FLAG_INCLUDE_PREFIX, 0, (*windows.IpAdapterAddresses)(unsafe.Pointer(&byt[0])), &length); err != nil {
|
||||
return nil, fmt.Errorf("config, windows: %w", err)
|
||||
}
|
||||
|
||||
var addresses []*windows.IpAdapterAddresses
|
||||
for addr := (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])); addr != nil; addr = addr.Next {
|
||||
//nolint:gosec // This is necessary unless we want to drop 1.18
|
||||
for addr := (*windows.IpAdapterAddresses)(unsafe.Pointer(&byt[0])); addr != nil; addr = addr.Next {
|
||||
addresses = append(addresses, addr)
|
||||
}
|
||||
|
||||
resolvers := map[string]bool{}
|
||||
|
||||
for _, addr := range addresses {
|
||||
for next := addr.FirstUnicastAddress; next != nil; next = next.Next {
|
||||
if addr.OperStatus != windows.IfOperStatusUp {
|
||||
continue
|
||||
}
|
||||
|
||||
if next.Address.IP() != nil {
|
||||
for dnsServer := addr.FirstDnsServerAddress; dnsServer != nil; dnsServer = dnsServer.Next {
|
||||
ip := dnsServer.Address.IP()
|
||||
|
||||
if ip.IsMulticast() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() || ip.IsUnspecified() {
|
||||
continue
|
||||
}
|
||||
|
||||
if ip.To16() != nil && strings.HasPrefix(ip.To16().String(), "fec0:") {
|
||||
continue
|
||||
}
|
||||
|
||||
resolvers[ip.String()] = true
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,9 @@ func TestWinConfig(t *testing.T) {
|
|||
if runtime.GOOS != "windows" {
|
||||
t.Skip("Not running Windows, skipping")
|
||||
}
|
||||
|
||||
conf, err := conf.GetDNSConfig()
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, len(conf.Servers) != 0)
|
||||
}
|
||||
|
|
244
doc/awl.1
244
doc/awl.1
|
@ -1,244 +0,0 @@
|
|||
.\" 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-08-03"
|
||||
.PP
|
||||
.SH NAME
|
||||
awl - DNS lookup tool
|
||||
.PP
|
||||
.SH SYNOPSIS
|
||||
\fBawl\fR [ \fIOPTIONS\fR ] \fIname\fR [ \fI@server\fR ] [ \fItype\fR ]
|
||||
.br
|
||||
where
|
||||
.PP
|
||||
\fIname\fR is the query to make (\fBexample: froth.\&zone\fR)
|
||||
.br
|
||||
\fI@server\fR is the server to query (\fBexample: dns.\&froth.\&zone\fR)
|
||||
.br
|
||||
\fItype\fR is the DNS resource type (\fBexample: AAAA\fR)
|
||||
.PP
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
\fBawl\fR (\fBa\fRwls \fBw\fRant \fBl\fRicorice) is a simple tool designed to make DNS queries,
|
||||
much like the venerable \fIdig\fR(1).\& An awl is a tool used to make small holes,
|
||||
typically used in leatherworking.\&
|
||||
.PP
|
||||
\fBawl\fR is designed to be a more "modern" version of \fIdrill\fR(1) by including
|
||||
some more recent RFCs and output options.\& \fBawl\fR is still heavily
|
||||
Work-In-Progress so some features may get added or removed.\&
|
||||
.PP
|
||||
.SH OPTIONS
|
||||
.RS 4
|
||||
Dig-like +[no]flags are supported, see dig(1)
|
||||
.PP
|
||||
\fB-D\fR, \fB--dnssec\fR, \fB+dnssec\fR
|
||||
.br
|
||||
Enable DNSSEC.\& This needs to be manually enabled.\&
|
||||
.PP
|
||||
\fB-v\fR \fIvalue\fR
|
||||
.br
|
||||
Set verbosity (currently WIP)
|
||||
.PP
|
||||
\fB-V\fR
|
||||
.br
|
||||
Print the version and exit.\&
|
||||
.PP
|
||||
\fB-h\fR
|
||||
.br
|
||||
Show a "short" help message.\&
|
||||
.PP
|
||||
.RE
|
||||
.SS Query Options
|
||||
.RS 4
|
||||
\fB-4\fR
|
||||
.br
|
||||
Only make query over IPv4
|
||||
.PP
|
||||
\fB-6\fR
|
||||
.br
|
||||
Only make query over IPv6
|
||||
.PP
|
||||
\fB-p\fR, \fB--port\fR \fIport\fR
|
||||
.br
|
||||
Sets the port to query.\&
|
||||
.br
|
||||
|
||||
.br
|
||||
\fIDefault Ports\fR:
|
||||
.RS 4
|
||||
.PD 0
|
||||
.IP \(bu 4
|
||||
\fI53\fR for \fBUDP\fR and \fBTCP\fR
|
||||
.IP \(bu 4
|
||||
\fI853\fR for \fBTLS\fR and \fBQUIC\fR
|
||||
.IP \(bu 4
|
||||
\fI443\fR for \fBHTTPS\fR
|
||||
.PD
|
||||
.PP
|
||||
.RE
|
||||
\fB-q\fR, \fB--query\fR \fIdomain\fR
|
||||
.br
|
||||
Domain to query (eg.\& example.\&com)
|
||||
.PP
|
||||
\fB-c\fR, \fB--class\fR \fIclass\fR
|
||||
.br
|
||||
DNS class to query (eg.\& IN, CH)
|
||||
.PP
|
||||
\fB-t\fR, \fB--qType\fR \fItype\fR
|
||||
.br
|
||||
DNS type to query (eg.\& A, NS)
|
||||
.PP
|
||||
\fB--no-truncate\fR, \fB+ignore\fR
|
||||
.br
|
||||
Ignore UDP truncation (by default, awl \fIretries with TCP\fR)
|
||||
.PP
|
||||
\fB--tcp\fR, \fB+tcp\fR, \fB+vc\fR
|
||||
.br
|
||||
Use TCP for the query (see \fIRFC 7766\fR)
|
||||
.PP
|
||||
\fB--dnscrypt\fR, \fB+dnscrypt\fR
|
||||
.br
|
||||
Use DNSCrypt
|
||||
.PP
|
||||
\fB-T\fR, \fB--tls\fR, \fB+tls\fR
|
||||
.br
|
||||
Use DNS-over-TLS, implies \fB--tcp\fR (see \fIRFC 7858\fR)
|
||||
.PP
|
||||
\fB-H\fR.\& \fB--https\fR, \fB+https\fR
|
||||
.br
|
||||
Use DNS-over-HTTPS (see \fIRFC 8484\fR)
|
||||
.PP
|
||||
\fB-Q\fR.\& \fB--quic\fR, \fB+quic\fR
|
||||
.br
|
||||
Use DNS-over-QUIC (see \fIRFC 9250\fR)
|
||||
.PP
|
||||
\fB-x\fR, \fB--reverse\fR
|
||||
.br
|
||||
Do a reverse lookup.\& Sets default \fItype\fR to PTR.\&
|
||||
.br
|
||||
\fBawl\fR automatically makes an IP or phone number canonical.\&
|
||||
.PP
|
||||
\fB--timeout\fR \fIseconds\fR, \fB+timeout=\fR\fIseconds\fR
|
||||
.br
|
||||
Set the timeout period.\& Floating point numbers are accepted.\&
|
||||
.br
|
||||
0.\&5 seconds is the minimum.\&
|
||||
.PP
|
||||
\fB--retries\fR \fIint\fR, \fB+tries\fR=\fIint\fR, \fB+ retry\fR=\fIint\fR
|
||||
.br
|
||||
Set the number of retries.\&
|
||||
.br
|
||||
Retry is one more than tries, dig style
|
||||
.PP
|
||||
.RE
|
||||
.SS DNS Flags
|
||||
.PP
|
||||
.RS 4
|
||||
\fB--aa=[false]\fR, \fB+[no]aaflag\fR
|
||||
.br
|
||||
(Set, Unset) AA (Authoritative Answer) flag
|
||||
.PP
|
||||
\fB--ad=[false]\fR, \fB+[no]adflag\fR
|
||||
.br
|
||||
(Set, Unset) AD (Authenticated Data) flag
|
||||
.PP
|
||||
\fB--tc=[false]\fR, \fB+[no]tcflag\fR
|
||||
.br
|
||||
(Set, Unset) TC (TrunCated) flag
|
||||
.PP
|
||||
\fB-z=[false]\fR, \fB+[no]zflag\fR
|
||||
.br
|
||||
(Set, Unset) Z (Zero) flag
|
||||
.PP
|
||||
\fB--cd=[false]\fR, \fB+[no]cdflag\fR
|
||||
.br
|
||||
(Set, Unset) CD (Checking Disabled) flag
|
||||
.PP
|
||||
\fB--qr=[false]\fR, \fB+[no]qrflag\fR
|
||||
.br
|
||||
(Set, Unset) QR (QueRy) flag
|
||||
.PP
|
||||
\fB--rd=[true]\fR, \fB+[no]rdflag\fR
|
||||
.br
|
||||
(Set, Unset) RD (Recursion Desired) flag
|
||||
.PP
|
||||
\fB--ra=[false]\fR, \fB+[no]raflag\fR
|
||||
.br
|
||||
(Set, Unset) RA (Recursion Available) flag
|
||||
.PP
|
||||
.RE
|
||||
.SS Output Display
|
||||
.RS 4
|
||||
\fB--no-question\fR, \fB+noquestion\fR
|
||||
.br
|
||||
Do not display the Question section
|
||||
.PP
|
||||
\fB--no-answer\fR, \fB+noanswer\fR
|
||||
.br
|
||||
Do not display the Answer section
|
||||
.PP
|
||||
\fB--no-answer\fR, \fB+noanswer\fR
|
||||
.br
|
||||
Do not display the Answer section
|
||||
.PP
|
||||
\fB--no-authority\fR, \fB+noauthority\fR
|
||||
.br
|
||||
Do not display the Authority section
|
||||
.PP
|
||||
\fB--no-additional\fR, \fB+noadditional\fR
|
||||
.br
|
||||
Do not display the Additional section
|
||||
.PP
|
||||
\fB--no-statistics\fR, \fB+nostats\fR
|
||||
.br
|
||||
Do not display the Statistics (additional comments) section
|
||||
.PP
|
||||
.RE
|
||||
.SS Output Formats
|
||||
.RS 4
|
||||
\fB-j\fR, \fB--json\fR, \fB+json\fR
|
||||
.br
|
||||
Print the query results as JSON.\&
|
||||
.PP
|
||||
\fB-X\fR, \fB--xml\fR, \fB+xml\fR
|
||||
.br
|
||||
Print the query results as XML.\&
|
||||
.PP
|
||||
\fB-y\fR, \fB--yaml\fR, \fB+yaml\fR
|
||||
.br
|
||||
Print the query results as YAML.\&
|
||||
.PP
|
||||
\fB-s\fR, \fB--short\fR, \fB+short\fR
|
||||
.br
|
||||
Print just the address of the answer.\&
|
||||
.PP
|
||||
.RE
|
||||
.SH EXAMPLES
|
||||
.nf
|
||||
.RS 4
|
||||
awl grumbulon\&.xyz -j +cd
|
||||
.fi
|
||||
.RE
|
||||
Run a query of your local resolver for the A records of grumbulon.\&xyz, print
|
||||
them as JSON and disable DNSSEC verification.\&
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
awl +short example\&.com AAAA @1\&.1\&.1\&.1
|
||||
.fi
|
||||
.RE
|
||||
Query 1.\&1.\&1.\&1 for the AAAA records of example.\&com, print just the answers
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
awl -xT PTR 8\&.8\&.4\&.4 @dns\&.google
|
||||
.fi
|
||||
.RE
|
||||
Query dns.\&google over TLS for the PTR record to the IP address 8.\&8.\&4.\&4
|
||||
.PP
|
||||
.SH SEE ALSO
|
||||
\fIdrill\fR(1), \fIdig\fR(1), the many DNS RFCs
|
185
doc/awl.1.scd
Normal file
185
doc/awl.1.scd
Normal file
|
@ -0,0 +1,185 @@
|
|||
awl(1)
|
||||
|
||||
# NAME
|
||||
|
||||
awl - DNS lookup tool
|
||||
|
||||
# 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 leatherworking.
|
||||
|
||||
_awl_ is designed to be a more "modern" version of _drill_(1) by including
|
||||
some more recent RFCs and output options. _awl_ is still heavily
|
||||
Work-In-Progress so some features may get added or removed.
|
||||
|
||||
# OPTIONS
|
||||
|
||||
Dig-like +[no]flags are supported, see dig(1)
|
||||
|
||||
_-D_, _--dnssec_, _+dnssec_
|
||||
Enable DNSSEC. This needs to be manually enabled.
|
||||
|
||||
_-v_ _value_
|
||||
Set verbosity (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 listed below.
|
||||
|
||||
- _53_ for _UDP_ and _TCP_
|
||||
- _853_ for _TLS_ and _QUIC_
|
||||
- _443_ for _HTTPS_
|
||||
|
||||
_-q_, _--query_ _domain_
|
||||
Domain to query (eg. example.com)
|
||||
|
||||
_-c_, _--class_ _class_
|
||||
DNS class to query (eg. IN, CH)
|
||||
|
||||
_-t_, _--qType_ _type_
|
||||
DNS type to query (eg. A, NS)
|
||||
|
||||
_--no-truncate_, _+ignore_
|
||||
Ignore UDP truncation (by default, awl _retries with TCP_)
|
||||
|
||||
_--tcp_, _+tcp_, _+vc_
|
||||
Use TCP for the query (see _RFC 7766_)
|
||||
|
||||
_--dnscrypt_, _+dnscrypt_
|
||||
Use DNSCrypt
|
||||
|
||||
_-T_, _--tls_, _+tls_
|
||||
Use DNS-over-TLS, implies _--tcp_ (see _RFC 7858_)
|
||||
|
||||
_--tls-host_ _string_
|
||||
Set hostname to use for TLS certificate validation.
|
||||
Default is the name of the domain when querying over TLS, and empty for IPs.
|
||||
|
||||
_--tls-no-verify_
|
||||
Ignore TLS validation when performing a DNS query.
|
||||
|
||||
_-H_. _--https_, _+https_
|
||||
Use DNS-over-HTTPS (see _RFC 8484_)
|
||||
|
||||
_-Q_. _--quic_, _+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.
|
||||
|
||||
_--timeout_ _seconds_, _+timeout=__seconds_
|
||||
Set the timeout period. Floating point numbers are accepted.++
|
||||
0.5 seconds is the minimum.
|
||||
|
||||
_--retries_ _int_, _+tries_=_int_, _+ retry_=_int_
|
||||
Set the number of retries.
|
||||
Retry is one more than tries, dig style
|
||||
|
||||
## DNS Flags
|
||||
|
||||
_--aa=[false]_, _+[no]aaflag_
|
||||
(Set, Unset) AA (Authoritative Answer) flag
|
||||
|
||||
_--ad=[false]_, _+[no]adflag_
|
||||
(Set, Unset) AD (Authenticated Data) flag
|
||||
|
||||
_--tc=[false]_, _+[no]tcflag_
|
||||
(Set, Unset) TC (TrunCated) flag
|
||||
|
||||
_-z=[false]_, _+[no]zflag_
|
||||
(Set, Unset) Z (Zero) flag
|
||||
|
||||
_--cd=[false]_, _+[no]cdflag_
|
||||
(Set, Unset) CD (Checking Disabled) flag
|
||||
|
||||
_--qr=[false]_, _+[no]qrflag_
|
||||
(Set, Unset) QR (QueRy) flag
|
||||
|
||||
_--rd=[true]_, _+[no]rdflag_
|
||||
(Set, Unset) RD (Recursion Desired) flag
|
||||
|
||||
_--ra=[false]_, _+[no]raflag_
|
||||
(Set, Unset) RA (Recursion Available) flag
|
||||
|
||||
## Output Display
|
||||
|
||||
_--no-question_, _+noquestion_
|
||||
Do not display the Question section
|
||||
|
||||
_--no-answer_, _+noanswer_
|
||||
Do not display the Answer section
|
||||
|
||||
_--no-answer_, _+noanswer_
|
||||
Do not display the Answer section
|
||||
|
||||
_--no-authority_, _+noauthority_
|
||||
Do not display the Authority section
|
||||
|
||||
_--no-additional_, _+noadditional_
|
||||
Do not display the Additional section
|
||||
|
||||
_--no-statistics_, _+nostats_
|
||||
Do not display the Statistics (additional comments) section
|
||||
|
||||
## Output Formats
|
||||
|
||||
_-j_, _--json_, _+json_
|
||||
Print the query results as JSON.
|
||||
|
||||
_-X_, _--xml_, _+xml_
|
||||
Print the query results as XML.
|
||||
|
||||
_-y_, _--yaml_, _+yaml_
|
||||
Print the query results as YAML.
|
||||
|
||||
_-s_, _--short_, _+short_
|
||||
Print just the address of the answer.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
awl grumbulon.xyz -j +cd
|
||||
```
|
||||
|
||||
Run a query of your local resolver for the A records of grumbulon.xyz, print
|
||||
them as JSON and disable DNSSEC verification.
|
||||
|
||||
```
|
||||
awl +short example.com AAAA @1.1.1.1
|
||||
```
|
||||
|
||||
Query 1.1.1.1 for the AAAA records of example.com, print just the answers
|
||||
|
||||
```
|
||||
awl -xT PTR 8.8.4.4 @dns.google
|
||||
```
|
||||
|
||||
Query dns.google over TLS for the PTR record to the IP address 8.8.4.4
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
_drill_(1), _dig_(1), the many DNS RFCs
|
2
doc/wiki
2
doc/wiki
|
@ -1 +1 @@
|
|||
Subproject commit 0fba1fbe4b12e8c88514b3f7d98be3e75a5a034d
|
||||
Subproject commit d25a7f13273737938d5e1e08c7332d39fffadc6b
|
|
@ -1,4 +0,0 @@
|
|||
/*
|
||||
Useful structs used everywhere I couldn't find a better place to shove
|
||||
*/
|
||||
package helpers
|
|
@ -1,25 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// The DNS response.
|
||||
type Response struct {
|
||||
DNS *dns.Msg // The full DNS response
|
||||
RTT time.Duration `json:"rtt"` // The time it took to make the DNS query
|
||||
}
|
||||
|
||||
// A structure for a DNS query.
|
||||
type Request struct {
|
||||
Server string `json:"server"` // The server to make the DNS request from
|
||||
Type uint16 `json:"request"` // The type of request
|
||||
Class uint16 `json:"class"` // DNS Class
|
||||
Name string `json:"name"` // The domain name to make a DNS request for
|
||||
Timeout time.Duration // The maximum timeout
|
||||
Retries int // Number of queries to retry
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package helpers_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestNothing(t *testing.T) {
|
||||
assert.Equal(t, 0, 0)
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
LogAwl is a package for custom logging needs
|
||||
Package logawl is a package for custom logging needs
|
||||
|
||||
LogAwl extends the standard log library with support for log levels
|
||||
This is _different_ from the syslog package in the standard library because you do not define a file
|
||||
|
@ -19,7 +19,8 @@ because awl is a cli utility it writes directly to std err.
|
|||
// You may also set the log level on the fly with
|
||||
//
|
||||
// Logger.SetLevel(3)
|
||||
// This allows you to change the default level (Info) and prevent log messages from being posted at higher verbosity levels
|
||||
// This allows you to change the default level (Info)
|
||||
// and prevent log messages from being posted at higher verbosity levels
|
||||
// for example if
|
||||
// Logger.SetLevel(3)
|
||||
// is not called and you call
|
||||
|
|
|
@ -10,23 +10,26 @@ import (
|
|||
)
|
||||
|
||||
type (
|
||||
Level int32
|
||||
// Level is the logging level.
|
||||
Level int32
|
||||
|
||||
// Logger is the overall logger.
|
||||
Logger struct {
|
||||
Out io.Writer
|
||||
Prefix string
|
||||
buf []byte
|
||||
Mu sync.Mutex
|
||||
Level Level
|
||||
isDiscard int32
|
||||
Prefix string
|
||||
Out io.Writer
|
||||
buf []byte
|
||||
}
|
||||
)
|
||||
|
||||
// Stores whatever input value is in mem address of l.level.
|
||||
// SetLevel stores whatever input value is in mem address of l.level.
|
||||
func (l *Logger) SetLevel(level Level) {
|
||||
atomic.StoreInt32((*int32)(&l.Level), int32(level))
|
||||
}
|
||||
|
||||
// Mostly nothing.
|
||||
// GetLevel gets the logger level.
|
||||
func (l *Logger) GetLevel() Level {
|
||||
return l.level()
|
||||
}
|
||||
|
@ -36,7 +39,7 @@ func (l *Logger) level() Level {
|
|||
return Level(atomic.LoadInt32((*int32)(&l.Level)))
|
||||
}
|
||||
|
||||
// Unmarshalls the int value of level for writing the header.
|
||||
// UnMarshalLevel unmarshalls the int value of level for writing the header.
|
||||
func (l *Logger) UnMarshalLevel(lv Level) (string, error) {
|
||||
switch lv {
|
||||
case 0:
|
||||
|
@ -48,13 +51,16 @@ func (l *Logger) UnMarshalLevel(lv Level) (string, error) {
|
|||
case 3:
|
||||
return "DEBUG ", nil
|
||||
}
|
||||
|
||||
return "", errInvalidLevel
|
||||
}
|
||||
|
||||
// IsLevel returns true if the logger level is above the level given.
|
||||
func (l *Logger) IsLevel(level Level) bool {
|
||||
return l.level() >= level
|
||||
}
|
||||
|
||||
// AllLevels is an array of all valid log levels.
|
||||
var AllLevels = []Level{
|
||||
ErrLevel,
|
||||
WarnLevel,
|
||||
|
@ -63,15 +69,21 @@ var AllLevels = []Level{
|
|||
}
|
||||
|
||||
const (
|
||||
// Fatal logs (will call exit(1)).
|
||||
// ErrLevel is the fatal (error) log level.
|
||||
ErrLevel Level = iota
|
||||
|
||||
// Error logs.
|
||||
// WarnLevel is for warning logs.
|
||||
//
|
||||
// Example: when one setting implies another, when a request fails but is retried.
|
||||
WarnLevel
|
||||
|
||||
// What is going on level.
|
||||
// InfoLevel is for saying what is going on when.
|
||||
// This is essentially the "verbose" option.
|
||||
//
|
||||
// When in doubt, use info.
|
||||
InfoLevel
|
||||
// Verbose log level.
|
||||
|
||||
// DebugLevel is for spewing debug structs/interfaces.
|
||||
DebugLevel
|
||||
)
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// Calling New instantiates Logger
|
||||
// New instantiates Logger
|
||||
//
|
||||
// Level can be changed to one of the other log levels (ErrorLevel, WarnLevel, InfoLevel, DebugLevel).
|
||||
func New() *Logger {
|
||||
|
@ -19,7 +19,7 @@ func New() *Logger {
|
|||
}
|
||||
}
|
||||
|
||||
// Takes any and prints it out to Logger -> Out (io.Writer (default is std.Err)).
|
||||
// Println takes any and prints it out to Logger -> Out (io.Writer (default is std.Err)).
|
||||
func (l *Logger) Println(level Level, v ...any) {
|
||||
if atomic.LoadInt32(&l.isDiscard) != 0 {
|
||||
return
|
||||
|
@ -27,24 +27,20 @@ func (l *Logger) Println(level Level, v ...any) {
|
|||
// If verbose is not set --debug etc print _nothing_
|
||||
if l.IsLevel(level) {
|
||||
switch level { // Goes through log levels and does stuff based on them (currently nothing)
|
||||
case 0:
|
||||
err := l.Printer(0, fmt.Sprintln(v...)) // Error level
|
||||
if err != nil {
|
||||
case ErrLevel:
|
||||
if err := l.Printer(ErrLevel, fmt.Sprintln(v...)); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Logger failed: ", err)
|
||||
}
|
||||
case 1:
|
||||
err := l.Printer(1, fmt.Sprintln(v...)) // Warn level
|
||||
if err != nil {
|
||||
case WarnLevel:
|
||||
if err := l.Printer(WarnLevel, fmt.Sprintln(v...)); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Logger failed: ", err)
|
||||
}
|
||||
case 2:
|
||||
err := l.Printer(2, fmt.Sprintln(v...)) // Info level
|
||||
if err != nil {
|
||||
case InfoLevel:
|
||||
if err := l.Printer(InfoLevel, fmt.Sprintln(v...)); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Logger failed: ", err)
|
||||
}
|
||||
case 3:
|
||||
err := l.Printer(3, fmt.Sprintln(v...)) // Debug level
|
||||
if err != nil {
|
||||
case DebugLevel:
|
||||
if err := l.Printer(DebugLevel, fmt.Sprintln(v...)); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Logger failed: ", err)
|
||||
}
|
||||
default:
|
||||
|
@ -53,13 +49,14 @@ 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>.
|
||||
// FormatHeader 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 {
|
||||
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
|
||||
*buf = append(*buf, lvl...)
|
||||
year, month, day := t.Date()
|
||||
|
||||
*buf = append(*buf, '[')
|
||||
formatter(buf, year, 4)
|
||||
*buf = append(*buf, '/')
|
||||
|
@ -79,46 +76,57 @@ func (l *Logger) FormatHeader(buf *[]byte, t time.Time, line int, level Level) e
|
|||
} else {
|
||||
return errInvalidLevel
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Printer prints the formatted message directly to stdErr.
|
||||
func (l *Logger) Printer(level Level, s string) error {
|
||||
now := time.Now()
|
||||
|
||||
var line int
|
||||
|
||||
l.Mu.Lock()
|
||||
defer l.Mu.Unlock()
|
||||
|
||||
l.buf = l.buf[:0]
|
||||
err := l.FormatHeader(&l.buf, now, line, level)
|
||||
if err != nil {
|
||||
|
||||
if err := l.FormatHeader(&l.buf, now, line, level); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.buf = append(l.buf, s...)
|
||||
|
||||
if len(s) == 0 || s[len(s)-1] != '\n' {
|
||||
l.buf = append(l.buf, '\n')
|
||||
}
|
||||
_, err = l.Out.Write(l.buf)
|
||||
|
||||
_, err := l.Out.Write(l.buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("logger printing error %w", err)
|
||||
return fmt.Errorf("logger printing: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Some line formatting stuff from Golang log stdlib file
|
||||
//
|
||||
// Please view https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/log/log.go;drc=41e1d9075e428c2fc32d966b3752a3029b620e2c;l=96
|
||||
// Please view
|
||||
// https://cs.opensource.google/go/go/+/refs/tags/go1.19:src/log/log.go;drc=41e1d9075e428c2fc32d966b3752a3029b620e2c;l=96
|
||||
//
|
||||
// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
|
||||
func formatter(buf *[]byte, i int, wid int) {
|
||||
// Assemble decimal in reverse order.
|
||||
var b [20]byte
|
||||
bp := len(b) - 1
|
||||
|
||||
for i >= 10 || wid > 1 {
|
||||
wid--
|
||||
|
||||
q := i / 10
|
||||
b[bp] = byte('0' + i - q*10)
|
||||
bp--
|
||||
|
||||
i = q
|
||||
}
|
||||
// i < 10
|
||||
|
@ -126,22 +134,22 @@ func formatter(buf *[]byte, i int, wid int) {
|
|||
*buf = append(*buf, b[bp:]...)
|
||||
}
|
||||
|
||||
// Call print directly with Debug level.
|
||||
// Debug calls print directly with Debug level.
|
||||
func (l *Logger) Debug(v ...any) {
|
||||
l.Println(DebugLevel, v...)
|
||||
}
|
||||
|
||||
// Call print directly with Info level.
|
||||
// Info calls print directly with Info level.
|
||||
func (l *Logger) Info(v ...any) {
|
||||
l.Println(InfoLevel, v...)
|
||||
}
|
||||
|
||||
// Call print directly with Warn level.
|
||||
// Warn calls print directly with Warn level.
|
||||
func (l *Logger) Warn(v ...any) {
|
||||
l.Println(WarnLevel, v...)
|
||||
}
|
||||
|
||||
// Call print directly with Error level.
|
||||
// Error calls print directly with Error level.
|
||||
func (l *Logger) Error(v ...any) {
|
||||
l.Println(ErrLevel, v...)
|
||||
}
|
||||
|
|
|
@ -24,10 +24,11 @@ func TestLogawl(t *testing.T) {
|
|||
|
||||
func TestUnmarshalLevels(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := make(map[int]string)
|
||||
var err error
|
||||
|
||||
for i := range logawl.AllLevels {
|
||||
var err error
|
||||
m[i], err = logger.UnMarshalLevel(logawl.Level(i))
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
@ -52,30 +53,42 @@ func TestLogger(t *testing.T) {
|
|||
fn := func() {
|
||||
logger.Error("Test", "E")
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
||||
logger.Out = &buffer
|
||||
|
||||
fn()
|
||||
case 1:
|
||||
fn := func() {
|
||||
logger.Warn("Test")
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
||||
logger.Out = &buffer
|
||||
|
||||
fn()
|
||||
case 2:
|
||||
fn := func() {
|
||||
logger.Info("Test")
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
||||
logger.Out = &buffer
|
||||
|
||||
fn()
|
||||
case 3:
|
||||
fn := func() {
|
||||
logger.Debug("Test")
|
||||
logger.Debug("Test 2")
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
||||
logger.Out = &buffer
|
||||
|
||||
fn()
|
||||
}
|
||||
}
|
||||
|
@ -83,6 +96,7 @@ func TestLogger(t *testing.T) {
|
|||
|
||||
func TestFmt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ti := time.Now()
|
||||
test := []byte("test")
|
||||
// make sure error is error
|
||||
|
|
27
main.go
27
main.go
|
@ -9,24 +9,31 @@ import (
|
|||
"strings"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"git.froth.zone/sam/awl/query"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
)
|
||||
|
||||
var version = "DEV"
|
||||
|
||||
func main() {
|
||||
opts, err := cli.ParseCLI(version)
|
||||
if err != nil {
|
||||
if opts, code, err := run(); err != nil {
|
||||
// TODO: Make not ew
|
||||
if errors.Is(err, cli.ErrNotError) || strings.Contains(err.Error(), "help requested") {
|
||||
os.Exit(0)
|
||||
} else {
|
||||
opts.Logger.Error(err)
|
||||
os.Exit(code)
|
||||
}
|
||||
opts.Logger.Error(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run() (opts util.Options, code int, err error) {
|
||||
opts, err = cli.ParseCLI(version)
|
||||
if err != nil {
|
||||
return opts, 1, fmt.Errorf("parse: %w", err)
|
||||
}
|
||||
|
||||
var resp helpers.Response
|
||||
var resp util.Response
|
||||
|
||||
// Retry queries if a query fails
|
||||
for i := 0; i < opts.Request.Retries; i++ {
|
||||
|
@ -40,20 +47,20 @@ func main() {
|
|||
|
||||
// Query failed, make it fail
|
||||
if err != nil {
|
||||
opts.Logger.Error(err)
|
||||
os.Exit(9)
|
||||
return opts, 9, fmt.Errorf("query: %w", err)
|
||||
}
|
||||
|
||||
var str string
|
||||
if opts.JSON || opts.XML || opts.YAML {
|
||||
str, err = query.PrintSpecial(resp.DNS, opts)
|
||||
if err != nil {
|
||||
opts.Logger.Error("Special print:", err)
|
||||
os.Exit(10)
|
||||
return opts, 10, fmt.Errorf("format print: %w", err)
|
||||
}
|
||||
} else {
|
||||
str = query.ToString(resp, opts)
|
||||
}
|
||||
|
||||
fmt.Println(str)
|
||||
|
||||
return opts, 0, nil
|
||||
}
|
||||
|
|
32
main_test.go
32
main_test.go
|
@ -6,14 +6,36 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stefansundin/go-zflag"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
// nolint: paralleltest
|
||||
func TestMain(t *testing.T) {
|
||||
func TestMain(t *testing.T) { //nolint: paralleltest // Race conditions
|
||||
old := os.Args
|
||||
|
||||
os.Args = []string{"awl", "+yaml", "@1.1.1.1"}
|
||||
main()
|
||||
|
||||
_, code, err := run()
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, code, 0)
|
||||
|
||||
os.Args = []string{"awl", "+short", "@1.1.1.1"}
|
||||
main()
|
||||
assert.Assert(t, 1 == 2-1)
|
||||
|
||||
_, code, err = run()
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, code, 0)
|
||||
|
||||
os.Args = old
|
||||
}
|
||||
|
||||
func TestHelp(t *testing.T) {
|
||||
old := os.Args
|
||||
|
||||
os.Args = []string{"awl", "-h"}
|
||||
|
||||
_, code, err := run()
|
||||
assert.ErrorIs(t, err, zflag.ErrHelp)
|
||||
assert.Equal(t, code, 1)
|
||||
|
||||
os.Args = old
|
||||
}
|
||||
|
|
|
@ -6,27 +6,29 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/ameshkov/dnscrypt/v2"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// DNSCryptResolver is for making DNSCrypt queries.
|
||||
type DNSCryptResolver struct {
|
||||
opts cli.Options
|
||||
opts util.Options
|
||||
}
|
||||
|
||||
var _ Resolver = (*DNSCryptResolver)(nil)
|
||||
|
||||
// LookUp performs a DNS query.
|
||||
func (r *DNSCryptResolver) LookUp(msg *dns.Msg) (helpers.Response, error) {
|
||||
func (r *DNSCryptResolver) LookUp(msg *dns.Msg) (util.Response, error) {
|
||||
client := dnscrypt.Client{
|
||||
Timeout: r.opts.Request.Timeout,
|
||||
UDPSize: 1232,
|
||||
}
|
||||
|
||||
if r.opts.TCP || r.opts.TLS {
|
||||
client.Net = "tcp"
|
||||
client.Net = tcp
|
||||
} else {
|
||||
client.Net = "udp"
|
||||
client.Net = udp
|
||||
}
|
||||
|
||||
switch {
|
||||
|
@ -35,11 +37,12 @@ func (r *DNSCryptResolver) LookUp(msg *dns.Msg) (helpers.Response, error) {
|
|||
case r.opts.IPv6:
|
||||
client.Net += "6"
|
||||
}
|
||||
|
||||
r.opts.Logger.Debug("Using", client.Net, "for making the request")
|
||||
|
||||
resolverInf, err := client.Dial(r.opts.Request.Server)
|
||||
if err != nil {
|
||||
return helpers.Response{}, fmt.Errorf("dnscrypt: dial error: %w", err)
|
||||
return util.Response{}, fmt.Errorf("dnscrypt: dial: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
@ -47,11 +50,12 @@ func (r *DNSCryptResolver) LookUp(msg *dns.Msg) (helpers.Response, error) {
|
|||
rtt := time.Since(now)
|
||||
|
||||
if err != nil {
|
||||
return helpers.Response{}, fmt.Errorf("dnscrypt: exchange error: %w", err)
|
||||
return util.Response{}, fmt.Errorf("dnscrypt: exchange: %w", err)
|
||||
}
|
||||
|
||||
r.opts.Logger.Info("Request successful")
|
||||
|
||||
return helpers.Response{
|
||||
return util.Response{
|
||||
DNS: res,
|
||||
RTT: rtt,
|
||||
}, nil
|
||||
|
|
|
@ -5,8 +5,6 @@ package query_test
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"git.froth.zone/sam/awl/query"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
|
@ -15,14 +13,15 @@ import (
|
|||
|
||||
func TestDNSCrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
opt cli.Options
|
||||
opt util.Options
|
||||
}{
|
||||
{
|
||||
cli.Options{
|
||||
util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
DNSCrypt: true,
|
||||
Request: helpers.Request{
|
||||
Request: util.Request{
|
||||
Server: "sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
|
@ -30,12 +29,12 @@ func TestDNSCrypt(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
cli.Options{
|
||||
util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
DNSCrypt: true,
|
||||
TCP: true,
|
||||
IPv4: true,
|
||||
Request: helpers.Request{
|
||||
Request: util.Request{
|
||||
Server: "sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||
Type: dns.TypeAAAA,
|
||||
Name: "example.com.",
|
||||
|
@ -43,12 +42,12 @@ func TestDNSCrypt(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
cli.Options{
|
||||
util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
DNSCrypt: true,
|
||||
TCP: true,
|
||||
IPv4: true,
|
||||
Request: helpers.Request{
|
||||
Request: util.Request{
|
||||
Server: "QMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||
Type: dns.TypeAAAA,
|
||||
Name: "example.com.",
|
||||
|
@ -56,13 +55,16 @@ func TestDNSCrypt(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run("", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
res, err := query.CreateQuery(test.opt)
|
||||
if err == nil {
|
||||
assert.Assert(t, res != helpers.Response{})
|
||||
assert.Assert(t, res != util.Response{})
|
||||
} else {
|
||||
assert.ErrorContains(t, err, "unsupported stamp")
|
||||
}
|
||||
|
|
|
@ -9,31 +9,37 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// HTTPSResolver is for DNS-over-HTTPS queries.
|
||||
type HTTPSResolver struct {
|
||||
opts cli.Options
|
||||
opts util.Options
|
||||
}
|
||||
|
||||
var _ Resolver = (*HTTPSResolver)(nil)
|
||||
|
||||
// LookUp performs a DNS query.
|
||||
func (r *HTTPSResolver) LookUp(msg *dns.Msg) (helpers.Response, error) {
|
||||
var resp helpers.Response
|
||||
func (r *HTTPSResolver) LookUp(msg *dns.Msg) (util.Response, error) {
|
||||
var resp util.Response
|
||||
|
||||
httpR := &http.Client{
|
||||
Timeout: r.opts.Request.Timeout,
|
||||
}
|
||||
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
return helpers.Response{}, fmt.Errorf("doh: packing error error: %w", err)
|
||||
return util.Response{}, fmt.Errorf("doh: packing: %w", err)
|
||||
}
|
||||
r.opts.Logger.Debug("making DoH request")
|
||||
//
|
||||
|
||||
r.opts.Logger.Debug("https: sending HTTPS request")
|
||||
|
||||
req, err := http.NewRequest("POST", r.opts.Request.Server, bytes.NewBuffer(buf))
|
||||
if err != nil {
|
||||
return helpers.Response{}, fmt.Errorf("doh request creation error: %w", err)
|
||||
return util.Response{}, fmt.Errorf("doh: request creation: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/dns-message")
|
||||
req.Header.Set("Accept", "application/dns-message")
|
||||
|
||||
|
@ -42,23 +48,32 @@ func (r *HTTPSResolver) LookUp(msg *dns.Msg) (helpers.Response, error) {
|
|||
resp.RTT = time.Since(now)
|
||||
|
||||
if err != nil {
|
||||
return helpers.Response{}, fmt.Errorf("doh HTTP request error: %w", err)
|
||||
return util.Response{}, fmt.Errorf("doh: HTTP request: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return helpers.Response{}, &errHTTPStatus{res.StatusCode}
|
||||
return util.Response{}, &errHTTPStatus{res.StatusCode}
|
||||
}
|
||||
|
||||
r.opts.Logger.Debug("https: reading response")
|
||||
|
||||
fullRes, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return helpers.Response{}, fmt.Errorf("doh body read error: %w", err)
|
||||
return util.Response{}, fmt.Errorf("doh: body read: %w", err)
|
||||
}
|
||||
|
||||
err = res.Body.Close()
|
||||
if err != nil {
|
||||
return util.Response{}, fmt.Errorf("doh: body close: %w", err)
|
||||
}
|
||||
|
||||
r.opts.Logger.Debug("https: unpacking response")
|
||||
|
||||
resp.DNS = &dns.Msg{}
|
||||
r.opts.Logger.Debug("unpacking response")
|
||||
|
||||
err = resp.DNS.Unpack(fullRes)
|
||||
if err != nil {
|
||||
return helpers.Response{}, fmt.Errorf("doh dns message unpack error: %w", err)
|
||||
return util.Response{}, fmt.Errorf("doh: dns message unpack: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
|
|
|
@ -3,12 +3,8 @@
|
|||
package query_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"git.froth.zone/sam/awl/query"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
|
@ -17,17 +13,19 @@ import (
|
|||
|
||||
func TestResolveHTTPS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var err error
|
||||
opts := cli.Options{
|
||||
|
||||
opts := util.Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Request: helpers.Request{
|
||||
Request: util.Request{
|
||||
Server: "https://dns9.quad9.net/dns-query",
|
||||
Type: dns.TypeA,
|
||||
Name: "git.froth.zone.",
|
||||
},
|
||||
}
|
||||
// testCase := helpers.Request{Server: "https://dns9.quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone."}
|
||||
// testCase := util.Request{Server: "https://dns9.quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone."}
|
||||
resolver, err := query.LoadResolver(opts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
|
@ -36,62 +34,75 @@ func TestResolveHTTPS(t *testing.T) {
|
|||
// msg = msg.SetQuestion(testCase.Name, testCase.Type)
|
||||
res, err := resolver.LookUp(msg)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, res != helpers.Response{})
|
||||
assert.Assert(t, res != util.Response{})
|
||||
}
|
||||
|
||||
func Test2ResolveHTTPS(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := cli.Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(0),
|
||||
|
||||
opts := util.Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Request: util.Request{Server: "dns9.quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone."},
|
||||
}
|
||||
|
||||
var err error
|
||||
testCase := helpers.Request{Server: "dns9.quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone"}
|
||||
|
||||
testCase := util.Request{Type: dns.TypeA, Name: "git.froth.zone"}
|
||||
resolver, err := query.LoadResolver(opts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
msg := new(dns.Msg)
|
||||
msg.SetQuestion(testCase.Name, testCase.Type)
|
||||
// msg = msg.SetQuestion(testCase.Name, testCase.Type)
|
||||
res, err := resolver.LookUp(msg)
|
||||
assert.ErrorContains(t, err, "fully qualified")
|
||||
assert.Equal(t, res, helpers.Response{})
|
||||
assert.Equal(t, res, util.Response{})
|
||||
}
|
||||
|
||||
func Test3ResolveHTTPS(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := cli.Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(0),
|
||||
|
||||
opts := util.Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Request: util.Request{Server: "dns9..quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone."},
|
||||
}
|
||||
|
||||
var err error
|
||||
testCase := helpers.Request{Server: "dns9..quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone."}
|
||||
|
||||
// testCase :=
|
||||
// if the domain is not canonical, make it canonical
|
||||
if !strings.HasSuffix(testCase.Name, ".") {
|
||||
testCase.Name = fmt.Sprintf("%s.", testCase.Name)
|
||||
}
|
||||
// if !strings.HasSuffix(testCase.Name, ".") {
|
||||
// testCase.Name = fmt.Sprintf("%s.", testCase.Name)
|
||||
// }
|
||||
|
||||
resolver, err := query.LoadResolver(opts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
msg := new(dns.Msg)
|
||||
msg.SetQuestion(testCase.Name, testCase.Type)
|
||||
// msg.SetQuestion(testCase.Name, testCase.Type)
|
||||
// msg = msg.SetQuestion(testCase.Name, testCase.Type)
|
||||
res, err := resolver.LookUp(msg)
|
||||
assert.ErrorContains(t, err, "request error")
|
||||
assert.Equal(t, res, helpers.Response{})
|
||||
assert.ErrorContains(t, err, "doh: HTTP request")
|
||||
assert.Equal(t, res, util.Response{})
|
||||
}
|
||||
|
||||
func Test404ResolveHTTPS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var err error
|
||||
opts := cli.Options{
|
||||
|
||||
opts := util.Options{
|
||||
HTTPS: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Request: helpers.Request{
|
||||
Request: util.Request{
|
||||
Server: "https://dns9.quad9.net/dns",
|
||||
Type: dns.TypeA,
|
||||
Name: "git.froth.zone.",
|
||||
},
|
||||
}
|
||||
// testCase := helpers.Request{Server: "https://dns9.quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone."}
|
||||
// testCase := util.Request{Server: "https://dns9.quad9.net/dns-query", Type: dns.TypeA, Name: "git.froth.zone."}
|
||||
resolver, err := query.LoadResolver(opts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
|
@ -100,5 +111,5 @@ func Test404ResolveHTTPS(t *testing.T) {
|
|||
// msg = msg.SetQuestion(testCase.Name, testCase.Type)
|
||||
res, err := resolver.LookUp(msg)
|
||||
assert.ErrorContains(t, err, "404")
|
||||
assert.Equal(t, res, helpers.Response{})
|
||||
assert.Equal(t, res, util.Response{})
|
||||
}
|
||||
|
|
|
@ -8,70 +8,95 @@ import (
|
|||
"io"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// QUICResolver is for DNS-over-QUIC queries.
|
||||
type QUICResolver struct {
|
||||
opts cli.Options
|
||||
opts util.Options
|
||||
}
|
||||
|
||||
var _ Resolver = (*QUICResolver)(nil)
|
||||
|
||||
// LookUp performs a DNS query.
|
||||
func (r *QUICResolver) LookUp(msg *dns.Msg) (helpers.Response, error) {
|
||||
var resp helpers.Response
|
||||
func (r *QUICResolver) LookUp(msg *dns.Msg) (util.Response, error) {
|
||||
var resp util.Response
|
||||
|
||||
tls := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
NextProtos: []string{"doq"},
|
||||
//nolint:gosec // This is intentional if the user requests it
|
||||
InsecureSkipVerify: r.opts.TLSNoVerify,
|
||||
ServerName: r.opts.TLSHost,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
NextProtos: []string{"doq"},
|
||||
}
|
||||
|
||||
conf := new(quic.Config)
|
||||
conf.HandshakeIdleTimeout = r.opts.Request.Timeout
|
||||
|
||||
r.opts.Logger.Debug("making DoQ request")
|
||||
r.opts.Logger.Debug("quic: making query")
|
||||
|
||||
connection, err := quic.DialAddr(r.opts.Request.Server, tls, conf)
|
||||
if err != nil {
|
||||
return helpers.Response{}, fmt.Errorf("doq: dial error: %w", err)
|
||||
return util.Response{}, fmt.Errorf("doq: dial: %w", err)
|
||||
}
|
||||
|
||||
r.opts.Logger.Debug("quic: packing query")
|
||||
|
||||
// Compress request to over-the-wire
|
||||
buf, err := msg.Pack()
|
||||
if err != nil {
|
||||
return helpers.Response{}, fmt.Errorf("doq: pack error: %w", err)
|
||||
return util.Response{}, fmt.Errorf("doq: pack: %w", err)
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
|
||||
r.opts.Logger.Debug("quic: creating stream")
|
||||
|
||||
stream, err := connection.OpenStream()
|
||||
if err != nil {
|
||||
return helpers.Response{}, fmt.Errorf("doq: quic stream creation error: %w", err)
|
||||
return util.Response{}, fmt.Errorf("doq: quic stream creation: %w", err)
|
||||
}
|
||||
|
||||
r.opts.Logger.Debug("quic: writing to stream")
|
||||
|
||||
_, err = stream.Write(buf)
|
||||
if err != nil {
|
||||
return helpers.Response{}, fmt.Errorf("doq: quic stream write error: %w", err)
|
||||
return util.Response{}, fmt.Errorf("doq: quic stream write: %w", err)
|
||||
}
|
||||
|
||||
r.opts.Logger.Debug("quic: reading stream")
|
||||
|
||||
fullRes, err := io.ReadAll(stream)
|
||||
if err != nil {
|
||||
return helpers.Response{}, fmt.Errorf("doq: quic stream read error: %w", err)
|
||||
return util.Response{}, fmt.Errorf("doq: quic stream read: %w", err)
|
||||
}
|
||||
|
||||
resp.RTT = time.Since(t)
|
||||
|
||||
r.opts.Logger.Debug("quic: closing connection")
|
||||
// Close with error: no error
|
||||
err = connection.CloseWithError(0, "")
|
||||
if err != nil {
|
||||
return helpers.Response{}, fmt.Errorf("doq: quic connection close error: %w", err)
|
||||
return util.Response{}, fmt.Errorf("doq: quic connection close: %w", err)
|
||||
}
|
||||
|
||||
r.opts.Logger.Debug("quic: closing stream")
|
||||
|
||||
err = stream.Close()
|
||||
if err != nil {
|
||||
return helpers.Response{}, fmt.Errorf("doq: quic stream close error: %w", err)
|
||||
return util.Response{}, fmt.Errorf("doq: quic stream close: %w", err)
|
||||
}
|
||||
|
||||
resp.DNS = &dns.Msg{}
|
||||
r.opts.Logger.Debug("unpacking DoQ response")
|
||||
|
||||
r.opts.Logger.Debug("quic: unpacking response")
|
||||
|
||||
err = resp.DNS.Unpack(fullRes)
|
||||
if err != nil {
|
||||
return helpers.Response{}, fmt.Errorf("doq: upack error: %w", err)
|
||||
return util.Response{}, fmt.Errorf("doq: unpack: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
|
|
@ -10,8 +10,6 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"git.froth.zone/sam/awl/query"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
|
@ -20,17 +18,21 @@ import (
|
|||
|
||||
func TestQuic(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := cli.Options{
|
||||
|
||||
opts := util.Options{
|
||||
QUIC: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 853,
|
||||
Request: helpers.Request{Server: "dns.adguard.com"},
|
||||
Request: util.Request{Server: "dns.adguard.com"},
|
||||
}
|
||||
testCase := helpers.Request{Server: "dns.//./,,adguard.com", Type: dns.TypeA, Name: "git.froth.zone"}
|
||||
testCase2 := helpers.Request{Server: "dns.adguard.com", Type: dns.TypeA, Name: "git.froth.zone"}
|
||||
var testCases []helpers.Request
|
||||
testCase := util.Request{Server: "dns.//./,,adguard.com", Type: dns.TypeA, Name: "git.froth.zone"}
|
||||
testCase2 := util.Request{Server: "dns.adguard.com", Type: dns.TypeA, Name: "git.froth.zone"}
|
||||
|
||||
var testCases []util.Request
|
||||
|
||||
testCases = append(testCases, testCase)
|
||||
testCases = append(testCases, testCase2)
|
||||
|
||||
for i := range testCases {
|
||||
switch i {
|
||||
case 0:
|
||||
|
@ -40,37 +42,44 @@ func TestQuic(t *testing.T) {
|
|||
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)
|
||||
// msg = msg.SetQuestion(testCase.Name, testCase.Type)
|
||||
res, err := resolver.LookUp(msg)
|
||||
|
||||
assert.ErrorContains(t, err, "fully qualified")
|
||||
assert.Equal(t, res, helpers.Response{})
|
||||
assert.Equal(t, res, util.Response{})
|
||||
case 1:
|
||||
resolver, err := query.LoadResolver(opts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
testCase2.Server = net.JoinHostPort(testCase2.Server, strconv.Itoa(opts.Port))
|
||||
|
||||
// if the domain is not canonical, make it canonical
|
||||
if !strings.HasSuffix(testCase2.Name, ".") {
|
||||
testCase2.Name = fmt.Sprintf("%s.", testCase2.Name)
|
||||
}
|
||||
|
||||
msg := new(dns.Msg)
|
||||
msg.SetQuestion(testCase2.Name, testCase2.Type)
|
||||
msg = msg.SetQuestion(testCase2.Name, testCase2.Type)
|
||||
|
||||
res, err := resolver.LookUp(msg)
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, res != helpers.Response{})
|
||||
assert.Assert(t, res != util.Response{})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidQuic(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := cli.Options{
|
||||
|
||||
opts := util.Options{
|
||||
QUIC: true,
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 853,
|
||||
Request: helpers.Request{Server: "example.com", Type: dns.TypeA, Name: "git.froth.zone", Timeout: 10 * time.Millisecond},
|
||||
Request: util.Request{Server: "example.com", Type: dns.TypeA, Name: "git.froth.zone", Timeout: 10 * time.Millisecond},
|
||||
}
|
||||
resolver, err := query.LoadResolver(opts)
|
||||
assert.NilError(t, err)
|
||||
|
@ -79,5 +88,5 @@ func TestInvalidQuic(t *testing.T) {
|
|||
msg.SetQuestion(opts.Request.Name, opts.Request.Type)
|
||||
res, err := resolver.LookUp(msg)
|
||||
assert.ErrorContains(t, err, "timeout")
|
||||
assert.Equal(t, res, helpers.Response{})
|
||||
assert.Equal(t, res, util.Response{})
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
// Package for the special query types
|
||||
// Package query is for the various query types.
|
||||
package query
|
||||
|
|
|
@ -3,32 +3,37 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// StandardResolver is for UDP/TCP resolvers.
|
||||
type StandardResolver struct {
|
||||
opts cli.Options
|
||||
opts util.Options
|
||||
}
|
||||
|
||||
// LookUp performs a DNS query
|
||||
func (r *StandardResolver) LookUp(msg *dns.Msg) (helpers.Response, error) {
|
||||
var _ Resolver = (*StandardResolver)(nil)
|
||||
|
||||
// LookUp performs a DNS query.
|
||||
func (r *StandardResolver) LookUp(msg *dns.Msg) (util.Response, error) {
|
||||
var (
|
||||
resp helpers.Response
|
||||
resp util.Response
|
||||
err error
|
||||
)
|
||||
|
||||
dnsClient := new(dns.Client)
|
||||
dnsClient.Dialer = &net.Dialer{
|
||||
Timeout: r.opts.Request.Timeout,
|
||||
}
|
||||
|
||||
if r.opts.TCP || r.opts.TLS {
|
||||
dnsClient.Net = "tcp"
|
||||
dnsClient.Net = tcp
|
||||
} else {
|
||||
dnsClient.Net = "udp"
|
||||
dnsClient.Net = udp
|
||||
}
|
||||
|
||||
switch {
|
||||
|
@ -40,28 +45,39 @@ func (r *StandardResolver) LookUp(msg *dns.Msg) (helpers.Response, error) {
|
|||
|
||||
if r.opts.TLS {
|
||||
dnsClient.Net += "-tls"
|
||||
dnsClient.TLSConfig = &tls.Config{
|
||||
//nolint:gosec // This is intentional if the user requests it
|
||||
InsecureSkipVerify: r.opts.TLSNoVerify,
|
||||
ServerName: r.opts.TLSHost,
|
||||
}
|
||||
}
|
||||
|
||||
r.opts.Logger.Debug("Using", dnsClient.Net, "for making the request")
|
||||
|
||||
resp.DNS, resp.RTT, err = dnsClient.Exchange(msg, r.opts.Request.Server)
|
||||
if err != nil {
|
||||
return helpers.Response{}, fmt.Errorf("standard: DNS exchange error: %w", err)
|
||||
return util.Response{}, fmt.Errorf("standard: DNS exchange: %w", err)
|
||||
}
|
||||
|
||||
r.opts.Logger.Info("Request successful")
|
||||
|
||||
if resp.DNS.MsgHdr.Truncated && !r.opts.Truncate {
|
||||
fmt.Printf(";; Truncated, retrying with TCP\n\n")
|
||||
dnsClient.Net = "tcp"
|
||||
|
||||
dnsClient.Net = tcp
|
||||
|
||||
switch {
|
||||
case r.opts.IPv4:
|
||||
dnsClient.Net += "4"
|
||||
case r.opts.IPv6:
|
||||
dnsClient.Net += "6"
|
||||
}
|
||||
|
||||
resp.DNS, resp.RTT, err = dnsClient.Exchange(msg, r.opts.Request.Server)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return helpers.Response{}, fmt.Errorf("standard: DNS exchange error: %w", err)
|
||||
return util.Response{}, fmt.Errorf("standard: DNS exchange: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
|
|
|
@ -6,8 +6,6 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"git.froth.zone/sam/awl/query"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
|
@ -16,10 +14,11 @@ import (
|
|||
|
||||
func TestResolve(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := cli.Options{
|
||||
|
||||
opts := util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 53,
|
||||
Request: helpers.Request{
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.1",
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
|
@ -29,19 +28,22 @@ func TestResolve(t *testing.T) {
|
|||
}
|
||||
resolver, err := query.LoadResolver(opts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
msg := new(dns.Msg)
|
||||
msg.SetQuestion(opts.Request.Name, opts.Request.Type)
|
||||
|
||||
_, err = resolver.LookUp(msg)
|
||||
assert.ErrorContains(t, err, "timeout")
|
||||
}
|
||||
|
||||
func TestTruncate(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := cli.Options{
|
||||
|
||||
opts := util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
IPv4: true,
|
||||
Port: 5301,
|
||||
Request: helpers.Request{
|
||||
Request: util.Request{
|
||||
Server: "madns.binarystar.systems",
|
||||
Type: dns.TypeTXT,
|
||||
Name: "limit.txt.example.",
|
||||
|
@ -49,24 +51,27 @@ func TestTruncate(t *testing.T) {
|
|||
}
|
||||
resolver, err := query.LoadResolver(opts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
msg := new(dns.Msg)
|
||||
msg.SetQuestion(opts.Request.Name, opts.Request.Type)
|
||||
res, err := resolver.LookUp(msg)
|
||||
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, res != helpers.Response{})
|
||||
assert.Assert(t, res != util.Response{})
|
||||
}
|
||||
|
||||
func TestResolveAgain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
opt cli.Options
|
||||
opt util.Options
|
||||
}{
|
||||
{
|
||||
cli.Options{
|
||||
util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
TCP: true,
|
||||
Port: 53,
|
||||
Request: helpers.Request{
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.4",
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
|
@ -74,10 +79,10 @@ func TestResolveAgain(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
cli.Options{
|
||||
util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 53,
|
||||
Request: helpers.Request{
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.4",
|
||||
Type: dns.TypeAAAA,
|
||||
Name: "example.com.",
|
||||
|
@ -85,11 +90,11 @@ func TestResolveAgain(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
cli.Options{
|
||||
util.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
TLS: true,
|
||||
Port: 853,
|
||||
Request: helpers.Request{
|
||||
Request: util.Request{
|
||||
Server: "dns.google",
|
||||
Type: dns.TypeAAAA,
|
||||
Name: "example.com.",
|
||||
|
@ -97,13 +102,15 @@ func TestResolveAgain(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run("", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res, err := query.CreateQuery(test.opt)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, res != helpers.Response{})
|
||||
assert.Assert(t, res != util.Response{})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,42 +11,49 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/net/idna"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func PrintSpecial(msg *dns.Msg, opts cli.Options) (string, error) {
|
||||
// PrintSpecial is for printing as JSON, XML or YAML.
|
||||
// As of now JSON and XML use the stdlib version.
|
||||
func PrintSpecial(msg *dns.Msg, opts util.Options) (string, error) {
|
||||
formatted, err := MakePrintable(msg, opts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch {
|
||||
case opts.JSON:
|
||||
opts.Logger.Info("Printing as JSON")
|
||||
json, err := json.MarshalIndent(formatted, " ", " ")
|
||||
return string(json), err
|
||||
|
||||
json, err := json.MarshalIndent(formatted, " ", " ")
|
||||
|
||||
return string(json), err
|
||||
case opts.XML:
|
||||
opts.Logger.Info("Printing as XML")
|
||||
xml, err := xml.MarshalIndent(formatted, " ", " ")
|
||||
return string(xml), err
|
||||
|
||||
xml, err := xml.MarshalIndent(formatted, " ", " ")
|
||||
|
||||
return string(xml), err
|
||||
case opts.YAML:
|
||||
opts.Logger.Info("Printing as YAML")
|
||||
yaml, err := yaml.Marshal(formatted)
|
||||
return string(yaml), err
|
||||
|
||||
yaml, err := yaml.Marshal(formatted)
|
||||
|
||||
return string(yaml), err
|
||||
default:
|
||||
return "", errInvalidFormat
|
||||
}
|
||||
}
|
||||
|
||||
// MakePrintable takes a DNS message and makes it nicer to be printed as JSON,YAML,
|
||||
// and XML. Little is changed beyond naming
|
||||
func MakePrintable(msg *dns.Msg, opts cli.Options) (*Message, error) {
|
||||
// and XML. Little is changed beyond naming.
|
||||
func MakePrintable(msg *dns.Msg, opts util.Options) (*Message, error) {
|
||||
var err error
|
||||
|
||||
ret := Message{
|
||||
Header: msg.MsgHdr,
|
||||
}
|
||||
|
@ -56,11 +63,12 @@ func MakePrintable(msg *dns.Msg, opts cli.Options) (*Message, error) {
|
|||
if opts.Display.UcodeTranslate {
|
||||
name, err = idna.ToUnicode(question.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("punycode: error translating to unicode: %w", err)
|
||||
return nil, fmt.Errorf("punycode to unicode: %w", err)
|
||||
}
|
||||
} else {
|
||||
name = question.Name
|
||||
}
|
||||
|
||||
ret.Question = append(ret.Question, Question{
|
||||
Name: name,
|
||||
Type: dns.TypeToString[question.Qtype],
|
||||
|
@ -70,10 +78,12 @@ func MakePrintable(msg *dns.Msg, opts cli.Options) (*Message, error) {
|
|||
|
||||
for _, answer := range msg.Answer {
|
||||
temp := strings.Split(answer.String(), "\t")
|
||||
|
||||
var (
|
||||
ttl string
|
||||
name string
|
||||
)
|
||||
|
||||
if opts.ShowTTL {
|
||||
if opts.HumanTTL {
|
||||
ttl = (time.Duration(answer.Header().Ttl) * time.Second).String()
|
||||
|
@ -81,14 +91,16 @@ func MakePrintable(msg *dns.Msg, opts cli.Options) (*Message, error) {
|
|||
ttl = strconv.Itoa(int(answer.Header().Ttl))
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.UcodeTranslate {
|
||||
name, err = idna.ToUnicode(answer.Header().Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("punycode: error translating to unicode: %w", err)
|
||||
return nil, fmt.Errorf("punycode to unicode: %w", err)
|
||||
}
|
||||
} else {
|
||||
name = answer.Header().Name
|
||||
}
|
||||
|
||||
ret.Answer = append(ret.Answer, Answer{
|
||||
RRHeader: RRHeader{
|
||||
Name: name,
|
||||
|
@ -103,10 +115,12 @@ func MakePrintable(msg *dns.Msg, opts cli.Options) (*Message, error) {
|
|||
|
||||
for _, ns := range msg.Ns {
|
||||
temp := strings.Split(ns.String(), "\t")
|
||||
|
||||
var (
|
||||
ttl string
|
||||
name string
|
||||
)
|
||||
|
||||
if opts.ShowTTL {
|
||||
if opts.HumanTTL {
|
||||
ttl = (time.Duration(ns.Header().Ttl) * time.Second).String()
|
||||
|
@ -114,14 +128,16 @@ func MakePrintable(msg *dns.Msg, opts cli.Options) (*Message, error) {
|
|||
ttl = strconv.Itoa(int(ns.Header().Ttl))
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.UcodeTranslate {
|
||||
name, err = idna.ToUnicode(ns.Header().Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("punycode: error translating to unicode: %w", err)
|
||||
return nil, fmt.Errorf("punycode to unicode: %w", err)
|
||||
}
|
||||
} else {
|
||||
name = ns.Header().Name
|
||||
}
|
||||
|
||||
ret.Ns = append(ret.Ns, Answer{
|
||||
RRHeader: RRHeader{
|
||||
Name: name,
|
||||
|
@ -139,10 +155,12 @@ func MakePrintable(msg *dns.Msg, opts cli.Options) (*Message, error) {
|
|||
continue
|
||||
} else {
|
||||
temp := strings.Split(additional.String(), "\t")
|
||||
|
||||
var (
|
||||
ttl string
|
||||
name string
|
||||
)
|
||||
|
||||
if opts.ShowTTL {
|
||||
if opts.HumanTTL {
|
||||
ttl = (time.Duration(additional.Header().Ttl) * time.Second).String()
|
||||
|
@ -150,14 +168,16 @@ func MakePrintable(msg *dns.Msg, opts cli.Options) (*Message, error) {
|
|||
ttl = strconv.Itoa(int(additional.Header().Ttl))
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.UcodeTranslate {
|
||||
name, err = idna.ToUnicode(additional.Header().Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("punycode: error translating to unicode: %w", err)
|
||||
return nil, fmt.Errorf("punycode to unicode: %w", err)
|
||||
}
|
||||
} else {
|
||||
name = additional.Header().Name
|
||||
}
|
||||
|
||||
ret.Extra = append(ret.Extra, Answer{
|
||||
RRHeader: RRHeader{
|
||||
Name: name,
|
||||
|
|
|
@ -5,339 +5,197 @@ package query_test
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"git.froth.zone/sam/awl/query"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestRealPrint(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts := []util.Options{
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 53,
|
||||
TCP: true,
|
||||
ShowQuery: true,
|
||||
RD: true,
|
||||
ShowTTL: true,
|
||||
HumanTTL: true,
|
||||
JSON: true,
|
||||
Display: util.Displays{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: true,
|
||||
},
|
||||
Request: util.Request{
|
||||
Server: "a.gtld-servers.net",
|
||||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "google.com.",
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 53,
|
||||
TCP: true,
|
||||
ShowQuery: true,
|
||||
RD: true,
|
||||
Verbosity: 0,
|
||||
ShowTTL: true,
|
||||
Short: true,
|
||||
Identify: true,
|
||||
YAML: false,
|
||||
Display: util.Displays{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: true,
|
||||
},
|
||||
Request: util.Request{
|
||||
Server: "ns1.google.com",
|
||||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "google.com.",
|
||||
Timeout: 0,
|
||||
Retries: 0,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 53,
|
||||
HTTPS: true,
|
||||
ShowQuery: true,
|
||||
RD: true,
|
||||
ShowTTL: true,
|
||||
HumanTTL: true,
|
||||
Identify: true,
|
||||
XML: true,
|
||||
Display: util.Displays{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: false,
|
||||
},
|
||||
Request: util.Request{
|
||||
Server: "https://dns.froth.zone/dns-query",
|
||||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "freecumextremist.com.",
|
||||
Timeout: 0,
|
||||
Retries: 0,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: false,
|
||||
DNSSEC: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 853,
|
||||
TLS: true,
|
||||
ShowQuery: true,
|
||||
RD: true,
|
||||
Verbosity: 0,
|
||||
ShowTTL: true,
|
||||
Identify: true,
|
||||
YAML: false,
|
||||
Display: util.Displays{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: true,
|
||||
},
|
||||
Request: util.Request{
|
||||
Server: "dns.google",
|
||||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "freecumextremist.com.",
|
||||
},
|
||||
},
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 53,
|
||||
TCP: true,
|
||||
ShowQuery: true,
|
||||
AA: true,
|
||||
RD: true,
|
||||
Verbosity: 0,
|
||||
ShowTTL: true,
|
||||
YAML: true,
|
||||
Display: util.Displays{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: false,
|
||||
},
|
||||
Request: util.Request{
|
||||
Server: "rin.froth.zone",
|
||||
Type: dns.StringToType["A"],
|
||||
Class: 1,
|
||||
Name: "froth.zone.",
|
||||
Timeout: 0,
|
||||
Retries: 0,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: true,
|
||||
Cookie: true,
|
||||
Padding: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range opts {
|
||||
test := test
|
||||
|
||||
t.Run("", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp, err := query.CreateQuery(test)
|
||||
assert.NilError(t, err)
|
||||
|
||||
if test.JSON || test.XML || test.YAML {
|
||||
str, err := query.PrintSpecial(resp.DNS, test)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, str != "")
|
||||
}
|
||||
str := query.ToString(resp, test)
|
||||
assert.Assert(t, str != "")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := query.PrintSpecial(new(dns.Msg), cli.Options{})
|
||||
|
||||
_, err := query.PrintSpecial(new(dns.Msg), util.Options{})
|
||||
assert.ErrorContains(t, err, "never happen")
|
||||
}
|
||||
|
||||
func TestPrinting(t *testing.T) {
|
||||
func TestEmpty(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := cli.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 53,
|
||||
IPv4: false,
|
||||
IPv6: false,
|
||||
TCP: true,
|
||||
DNSCrypt: false,
|
||||
TLS: false,
|
||||
HTTPS: false,
|
||||
QUIC: false,
|
||||
Truncate: false,
|
||||
ShowQuery: true,
|
||||
AA: false,
|
||||
AD: false,
|
||||
CD: false,
|
||||
QR: false,
|
||||
RD: true,
|
||||
RA: false,
|
||||
TC: false,
|
||||
Z: false,
|
||||
Reverse: false,
|
||||
Verbosity: 0,
|
||||
HumanTTL: false,
|
||||
ShowTTL: true,
|
||||
Short: false,
|
||||
Identify: false,
|
||||
JSON: true,
|
||||
XML: false,
|
||||
YAML: false,
|
||||
Display: cli.Displays{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: true,
|
||||
},
|
||||
Request: helpers.Request{
|
||||
Server: "a.gtld-servers.net",
|
||||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "google.com.",
|
||||
Timeout: 0,
|
||||
Retries: 0,
|
||||
},
|
||||
EDNS: cli.EDNS{
|
||||
EnableEDNS: false,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := query.CreateQuery(opts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
str, err := query.PrintSpecial(resp.DNS, opts)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, str != "")
|
||||
}
|
||||
|
||||
func TestPrinting2(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := cli.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 53,
|
||||
IPv4: false,
|
||||
IPv6: false,
|
||||
TCP: true,
|
||||
DNSCrypt: false,
|
||||
TLS: false,
|
||||
HTTPS: false,
|
||||
QUIC: false,
|
||||
Truncate: false,
|
||||
ShowQuery: true,
|
||||
AA: false,
|
||||
AD: false,
|
||||
CD: false,
|
||||
QR: false,
|
||||
RD: true,
|
||||
RA: false,
|
||||
TC: false,
|
||||
Z: false,
|
||||
Reverse: false,
|
||||
Verbosity: 0,
|
||||
HumanTTL: false,
|
||||
ShowTTL: true,
|
||||
Short: true,
|
||||
Identify: true,
|
||||
JSON: false,
|
||||
XML: false,
|
||||
YAML: true,
|
||||
Display: cli.Displays{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: true,
|
||||
},
|
||||
Request: helpers.Request{
|
||||
Server: "ns1.google.com",
|
||||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "google.com.",
|
||||
Timeout: 0,
|
||||
Retries: 0,
|
||||
},
|
||||
EDNS: cli.EDNS{
|
||||
EnableEDNS: false,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := query.CreateQuery(opts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
str, err := query.PrintSpecial(resp.DNS, opts)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, str != "")
|
||||
|
||||
str = query.ToString(resp, opts)
|
||||
assert.Assert(t, str != "")
|
||||
}
|
||||
|
||||
func TestPrinting3(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := cli.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 53,
|
||||
IPv4: false,
|
||||
IPv6: false,
|
||||
TCP: false,
|
||||
DNSCrypt: false,
|
||||
TLS: false,
|
||||
HTTPS: true,
|
||||
QUIC: false,
|
||||
Truncate: false,
|
||||
ShowQuery: true,
|
||||
AA: false,
|
||||
AD: false,
|
||||
CD: false,
|
||||
QR: false,
|
||||
RD: true,
|
||||
RA: false,
|
||||
TC: false,
|
||||
Z: false,
|
||||
Reverse: false,
|
||||
Verbosity: 0,
|
||||
HumanTTL: false,
|
||||
ShowTTL: true,
|
||||
Short: false,
|
||||
Identify: true,
|
||||
JSON: false,
|
||||
XML: false,
|
||||
YAML: true,
|
||||
Display: cli.Displays{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: true,
|
||||
},
|
||||
Request: helpers.Request{
|
||||
Server: "https://dns.froth.zone/dns-query",
|
||||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "freecumextremist.com.",
|
||||
Timeout: 0,
|
||||
Retries: 0,
|
||||
},
|
||||
EDNS: cli.EDNS{
|
||||
EnableEDNS: false,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := query.CreateQuery(opts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
str, err := query.PrintSpecial(resp.DNS, opts)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, str != "")
|
||||
|
||||
str = query.ToString(resp, opts)
|
||||
assert.Assert(t, str != "")
|
||||
}
|
||||
|
||||
func TestPrinting4(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := cli.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 853,
|
||||
IPv4: false,
|
||||
IPv6: false,
|
||||
TCP: false,
|
||||
DNSCrypt: false,
|
||||
TLS: true,
|
||||
HTTPS: false,
|
||||
QUIC: false,
|
||||
Truncate: false,
|
||||
ShowQuery: true,
|
||||
AA: false,
|
||||
AD: false,
|
||||
CD: false,
|
||||
QR: false,
|
||||
RD: true,
|
||||
RA: false,
|
||||
TC: false,
|
||||
Z: false,
|
||||
Reverse: false,
|
||||
Verbosity: 0,
|
||||
HumanTTL: false,
|
||||
ShowTTL: true,
|
||||
Short: false,
|
||||
Identify: true,
|
||||
JSON: false,
|
||||
XML: false,
|
||||
YAML: true,
|
||||
Display: cli.Displays{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: true,
|
||||
},
|
||||
Request: helpers.Request{
|
||||
Server: "dns.google",
|
||||
Type: dns.StringToType["NS"],
|
||||
Class: 1,
|
||||
Name: "freecumextremist.com.",
|
||||
Timeout: 0,
|
||||
Retries: 0,
|
||||
},
|
||||
EDNS: cli.EDNS{
|
||||
EnableEDNS: false,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := query.CreateQuery(opts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
str, err := query.PrintSpecial(resp.DNS, opts)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, str != "")
|
||||
|
||||
str = query.ToString(resp, opts)
|
||||
assert.Assert(t, str != "")
|
||||
}
|
||||
|
||||
func TestPrinting5(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := cli.Options{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 53,
|
||||
IPv4: false,
|
||||
IPv6: false,
|
||||
TCP: true,
|
||||
DNSCrypt: false,
|
||||
TLS: false,
|
||||
HTTPS: false,
|
||||
QUIC: false,
|
||||
Truncate: false,
|
||||
ShowQuery: true,
|
||||
AA: true,
|
||||
AD: false,
|
||||
CD: false,
|
||||
QR: false,
|
||||
RD: true,
|
||||
RA: false,
|
||||
TC: false,
|
||||
Z: false,
|
||||
Reverse: false,
|
||||
Verbosity: 0,
|
||||
HumanTTL: false,
|
||||
ShowTTL: true,
|
||||
Short: false,
|
||||
Identify: false,
|
||||
JSON: false,
|
||||
XML: false,
|
||||
YAML: true,
|
||||
Display: cli.Displays{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: true,
|
||||
},
|
||||
Request: helpers.Request{
|
||||
Server: "rin.froth.zone",
|
||||
Type: dns.StringToType["A"],
|
||||
Class: 1,
|
||||
Name: "froth.zone.",
|
||||
Timeout: 0,
|
||||
Retries: 0,
|
||||
},
|
||||
EDNS: cli.EDNS{
|
||||
EnableEDNS: true,
|
||||
Cookie: true,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := query.CreateQuery(opts)
|
||||
assert.NilError(t, err)
|
||||
|
||||
str, err := query.PrintSpecial(resp.DNS, opts)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, str != "")
|
||||
|
||||
str = query.ToString(resp, opts)
|
||||
assert.Assert(t, str != "")
|
||||
}
|
||||
|
||||
func TestToString6(t *testing.T) {
|
||||
assert.Assert(t, query.ToString(*new(helpers.Response), *new(cli.Options)) == "<nil> MsgHdr")
|
||||
assert.Assert(t, query.ToString(util.Response{}, util.Options{}) == "<nil> MsgHdr")
|
||||
}
|
||||
|
|
|
@ -6,13 +6,19 @@ import (
|
|||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/dchest/uniuri"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func CreateQuery(opts cli.Options) (helpers.Response, error) {
|
||||
const (
|
||||
tcp = "tcp"
|
||||
udp = "udp"
|
||||
)
|
||||
|
||||
// CreateQuery creates a DNS query from the options given.
|
||||
// It sets query flags and EDNS flags from the respective options.
|
||||
func CreateQuery(opts util.Options) (util.Response, error) {
|
||||
req := new(dns.Msg)
|
||||
req.SetQuestion(opts.Request.Name, opts.Request.Type)
|
||||
req.Question[0].Qclass = opts.Request.Class
|
||||
|
@ -40,37 +46,45 @@ func CreateQuery(opts cli.Options) (helpers.Response, error) {
|
|||
e.Code = dns.EDNS0COOKIE
|
||||
e.Cookie = uniuri.NewLenChars(8, []byte("1234567890abcdef"))
|
||||
o.Option = append(o.Option, e)
|
||||
|
||||
opts.Logger.Info("Setting EDNS cookie to", e.Cookie)
|
||||
}
|
||||
|
||||
if opts.EDNS.Expire {
|
||||
o.Option = append(o.Option, new(dns.EDNS0_EXPIRE))
|
||||
|
||||
opts.Logger.Info("Setting EDNS Expire option")
|
||||
}
|
||||
|
||||
if opts.EDNS.KeepOpen {
|
||||
o.Option = append(o.Option, new(dns.EDNS0_TCP_KEEPALIVE))
|
||||
|
||||
opts.Logger.Info("Setting EDNS TCP Keepalive option")
|
||||
}
|
||||
|
||||
if opts.EDNS.Nsid {
|
||||
o.Option = append(o.Option, new(dns.EDNS0_NSID))
|
||||
|
||||
opts.Logger.Info("Setting EDNS NSID option")
|
||||
}
|
||||
|
||||
if opts.EDNS.Padding {
|
||||
o.Option = append(o.Option, new(dns.EDNS0_PADDING))
|
||||
|
||||
opts.Logger.Info("Setting EDNS padding")
|
||||
}
|
||||
|
||||
o.SetUDPSize(opts.BufSize)
|
||||
|
||||
opts.Logger.Info("EDNS UDP buffer set to", opts.BufSize)
|
||||
|
||||
o.SetZ(opts.EDNS.ZFlag)
|
||||
|
||||
opts.Logger.Info("EDNS Z flag set to", opts.EDNS.ZFlag)
|
||||
|
||||
if opts.EDNS.DNSSEC {
|
||||
o.SetDo()
|
||||
|
||||
opts.Logger.Info("EDNS DNSSEC OK set")
|
||||
}
|
||||
|
||||
|
@ -84,39 +98,45 @@ func CreateQuery(opts cli.Options) (helpers.Response, error) {
|
|||
opts.Logger.Warn("DNSSEC implies EDNS, EDNS enabled")
|
||||
opts.Logger.Info("DNSSEC enabled, UDP buffer set to 1232")
|
||||
}
|
||||
|
||||
opts.Logger.Debug(req)
|
||||
|
||||
if !opts.Short {
|
||||
if opts.ShowQuery {
|
||||
opts.Logger.Info("Printing constructed query")
|
||||
|
||||
var (
|
||||
str string
|
||||
err error
|
||||
)
|
||||
|
||||
if opts.JSON || opts.XML || opts.YAML {
|
||||
str, err = PrintSpecial(req, opts)
|
||||
if err != nil {
|
||||
return helpers.Response{}, err
|
||||
return util.Response{}, err
|
||||
}
|
||||
} else {
|
||||
temp := opts.Display.Statistics
|
||||
opts.Display.Statistics = false
|
||||
str = ToString(helpers.Response{
|
||||
str = ToString(util.Response{
|
||||
DNS: req,
|
||||
RTT: 0,
|
||||
}, opts)
|
||||
opts.Display.Statistics = temp
|
||||
str += "\n;; QUERY SIZE: " + strconv.Itoa(req.Len())
|
||||
str += "\n;; QUERY SIZE: " + strconv.Itoa(req.Len()) + "\n"
|
||||
}
|
||||
|
||||
fmt.Println(str)
|
||||
|
||||
opts.ShowQuery = false
|
||||
}
|
||||
}
|
||||
|
||||
resolver, err := LoadResolver(opts)
|
||||
if err != nil {
|
||||
return helpers.Response{}, err
|
||||
return util.Response{}, err
|
||||
}
|
||||
|
||||
opts.Logger.Info("Query successfully loaded")
|
||||
|
||||
//nolint:wrapcheck // Error wrapping not needed here
|
||||
|
|
|
@ -5,8 +5,6 @@ package query_test
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"git.froth.zone/sam/awl/query"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
|
@ -15,32 +13,30 @@ import (
|
|||
|
||||
func TestCreateQ(t *testing.T) {
|
||||
t.Parallel()
|
||||
in := []cli.Options{
|
||||
|
||||
in := []util.Options{
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 53,
|
||||
QR: false,
|
||||
Z: true,
|
||||
RD: false,
|
||||
ShowQuery: true,
|
||||
YAML: true,
|
||||
|
||||
Request: helpers.Request{
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.4",
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
},
|
||||
Display: cli.Displays{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Opt: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
UcodeTranslate: false,
|
||||
Display: util.Displays{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Opt: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
},
|
||||
EDNS: cli.EDNS{
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: true,
|
||||
DNSSEC: true,
|
||||
Cookie: true,
|
||||
|
@ -52,18 +48,16 @@ func TestCreateQ(t *testing.T) {
|
|||
{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 53,
|
||||
QR: false,
|
||||
Z: true,
|
||||
RD: false,
|
||||
ShowQuery: true,
|
||||
XML: true,
|
||||
|
||||
Request: helpers.Request{
|
||||
Request: util.Request{
|
||||
Server: "8.8.4.4",
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
},
|
||||
Display: cli.Displays{
|
||||
Display: util.Displays{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Opt: true,
|
||||
|
@ -73,26 +67,52 @@ func TestCreateQ(t *testing.T) {
|
|||
Statistics: true,
|
||||
UcodeTranslate: true,
|
||||
},
|
||||
EDNS: cli.EDNS{
|
||||
EnableEDNS: false,
|
||||
DNSSEC: false,
|
||||
Cookie: false,
|
||||
Expire: false,
|
||||
KeepOpen: false,
|
||||
Nsid: false,
|
||||
},
|
||||
{
|
||||
Logger: util.InitLogger(0),
|
||||
Port: 853,
|
||||
// Z: true,
|
||||
ShowQuery: true,
|
||||
JSON: true,
|
||||
QUIC: true,
|
||||
|
||||
Request: util.Request{
|
||||
Server: "dns.adguard.com",
|
||||
Type: dns.TypeA,
|
||||
Name: "example.com.",
|
||||
},
|
||||
Display: util.Displays{
|
||||
Comments: true,
|
||||
Question: true,
|
||||
Opt: true,
|
||||
Answer: true,
|
||||
Authority: true,
|
||||
Additional: true,
|
||||
Statistics: true,
|
||||
},
|
||||
EDNS: util.EDNS{
|
||||
EnableEDNS: true,
|
||||
DNSSEC: true,
|
||||
Cookie: true,
|
||||
Expire: true,
|
||||
Nsid: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range in {
|
||||
opt := opt
|
||||
|
||||
t.Run("", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res, err := query.CreateQuery(opt)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, res != helpers.Response{})
|
||||
assert.Assert(t, res != util.Response{})
|
||||
|
||||
str, err := query.PrintSpecial(res.DNS, opt)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, str != "")
|
||||
|
||||
str = query.ToString(res, opt)
|
||||
assert.Assert(t, str != "")
|
||||
})
|
||||
|
|
|
@ -7,44 +7,49 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Main resolver interface
|
||||
// Resolver is the main resolver interface.
|
||||
type Resolver interface {
|
||||
LookUp(*dns.Msg) (helpers.Response, error)
|
||||
LookUp(*dns.Msg) (util.Response, error)
|
||||
}
|
||||
|
||||
// LoadResolver loads the respective resolver for performing a DNS query
|
||||
func LoadResolver(opts cli.Options) (Resolver, error) {
|
||||
// LoadResolver loads the respective resolver for performing a DNS query.
|
||||
func LoadResolver(opts util.Options) (Resolver, error) {
|
||||
switch {
|
||||
case opts.HTTPS:
|
||||
opts.Logger.Info("loading DNS-over-HTTPS resolver")
|
||||
|
||||
if !strings.HasPrefix(opts.Request.Server, "https://") {
|
||||
opts.Request.Server = "https://" + opts.Request.Server
|
||||
}
|
||||
|
||||
return &HTTPSResolver{
|
||||
opts: opts,
|
||||
}, nil
|
||||
case opts.QUIC:
|
||||
opts.Logger.Info("loading DNS-over-QUIC resolver")
|
||||
opts.Request.Server = net.JoinHostPort(opts.Request.Server, strconv.Itoa(opts.Port))
|
||||
|
||||
return &QUICResolver{
|
||||
opts: opts,
|
||||
}, nil
|
||||
case opts.DNSCrypt:
|
||||
opts.Logger.Info("loading DNSCrypt resolver")
|
||||
|
||||
if !strings.HasPrefix(opts.Request.Server, "sdns://") {
|
||||
opts.Request.Server = "sdns://" + opts.Request.Server
|
||||
}
|
||||
|
||||
return &DNSCryptResolver{
|
||||
opts: opts,
|
||||
}, nil
|
||||
default:
|
||||
opts.Logger.Info("loading standard/DNS-over-TLS resolver")
|
||||
opts.Request.Server = net.JoinHostPort(opts.Request.Server, strconv.Itoa(opts.Port))
|
||||
|
||||
return &StandardResolver{
|
||||
opts: opts,
|
||||
}, nil
|
||||
|
|
|
@ -7,28 +7,29 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"git.froth.zone/sam/awl/cli"
|
||||
"git.froth.zone/sam/awl/internal/helpers"
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Overall DNS response message
|
||||
// Message is for overall DNS responses.
|
||||
//
|
||||
//nolint:fieldalignment // IMO this looks better when printed like this
|
||||
type Message struct {
|
||||
Header dns.MsgHdr `json:"header,omitempty" xml:"header,omitempty" yaml:",omitempty"`
|
||||
Question []Question `json:"question,omitempty" xml:"question,omitempty" yaml:",omitempty"`
|
||||
Answer []Answer `json:"answer,omitempty" xml:"answer,omitempty" yaml:",omitempty"`
|
||||
Ns []Answer `json:"ns,omitempty" xml:"ns,omitempty" yaml:",omitempty"`
|
||||
Extra []Answer `json:"extra,omitempty" xml:"extra,omitempty" yaml:",omitempty"`
|
||||
Header dns.MsgHdr `json:"header,omitempty" xml:"header,omitempty" yaml:",omitempty"`
|
||||
}
|
||||
|
||||
// DNS Query
|
||||
// Question is a DNS Query.
|
||||
type Question struct {
|
||||
Name string `json:"name,omitempty" xml:"name,omitempty" yaml:",omitempty"`
|
||||
Type string `json:"type,omitempty" xml:"type,omitempty" yaml:",omitempty"`
|
||||
Class string `json:"class,omitempty" xml:"class,omitempty" yaml:",omitempty"`
|
||||
}
|
||||
|
||||
// DNS Resource Headers
|
||||
// RRHeader is for DNS Resource Headers.
|
||||
type RRHeader struct {
|
||||
Name string `json:"name,omitempty" xml:"name,omitempty" yaml:",omitempty"`
|
||||
Type string `json:"type,omitempty" xml:"type,omitempty" yaml:",omitempty"`
|
||||
|
@ -37,21 +38,26 @@ type RRHeader struct {
|
|||
Rdlength uint16 `json:"-" xml:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// DNS Response
|
||||
// Answer is for a DNS Response.
|
||||
//
|
||||
//nolint:fieldalignment // IMO this looks better when printed like this
|
||||
type Answer struct {
|
||||
RRHeader `json:"header,omitempty" xml:"header,omitempty" yaml:"header,omitempty"`
|
||||
Value string `json:"response,omitempty" xml:"response,omitempty" yaml:"response,omitempty"`
|
||||
RRHeader `json:"header,omitempty" xml:"header,omitempty" yaml:"header,omitempty"`
|
||||
}
|
||||
|
||||
// ToString turns the response into something that looks a lot like dig
|
||||
//
|
||||
// Much of this is taken from https://github.com/miekg/dns/blob/master/msg.go#L900
|
||||
func ToString(res helpers.Response, opts cli.Options) string {
|
||||
func ToString(res util.Response, opts util.Options) string {
|
||||
if res.DNS == nil {
|
||||
return "<nil> MsgHdr"
|
||||
}
|
||||
var s string
|
||||
var opt *dns.OPT
|
||||
|
||||
var (
|
||||
s string
|
||||
opt *dns.OPT
|
||||
)
|
||||
|
||||
if !opts.Short {
|
||||
if opts.Display.Comments {
|
||||
|
@ -61,26 +67,31 @@ func ToString(res helpers.Response, opts cli.Options) string {
|
|||
s += "AUTHORITY: " + strconv.Itoa(len(res.DNS.Ns)) + ", "
|
||||
s += "ADDITIONAL: " + strconv.Itoa(len(res.DNS.Extra)) + "\n"
|
||||
opt = res.DNS.IsEdns0()
|
||||
|
||||
if opt != nil && opts.Display.Opt {
|
||||
// OPT PSEUDOSECTION
|
||||
s += opt.String() + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.Question {
|
||||
if len(res.DNS.Question) > 0 {
|
||||
if opts.Display.Comments {
|
||||
s += "\n;; QUESTION SECTION:\n"
|
||||
}
|
||||
|
||||
for _, r := range res.DNS.Question {
|
||||
s += r.String() + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.Answer {
|
||||
if len(res.DNS.Answer) > 0 {
|
||||
if opts.Display.Comments {
|
||||
s += "\n;; ANSWER SECTION:\n"
|
||||
}
|
||||
|
||||
for _, r := range res.DNS.Answer {
|
||||
if r != nil {
|
||||
s += r.String() + "\n"
|
||||
|
@ -88,11 +99,13 @@ func ToString(res helpers.Response, opts cli.Options) string {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.Authority {
|
||||
if len(res.DNS.Ns) > 0 {
|
||||
if opts.Display.Comments {
|
||||
s += "\n;; AUTHORITY SECTION:\n"
|
||||
}
|
||||
|
||||
for _, r := range res.DNS.Ns {
|
||||
if r != nil {
|
||||
s += r.String() + "\n"
|
||||
|
@ -100,11 +113,13 @@ func ToString(res helpers.Response, opts cli.Options) string {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.Additional {
|
||||
if len(res.DNS.Extra) > 0 && (opt == nil || len(res.DNS.Extra) > 1) {
|
||||
if opts.Display.Comments {
|
||||
s += "\n;; ADDITIONAL SECTION:\n"
|
||||
}
|
||||
|
||||
for _, r := range res.DNS.Extra {
|
||||
if r != nil && r.Header().Rrtype != dns.TypeOPT {
|
||||
s += r.String() + "\n"
|
||||
|
@ -112,10 +127,12 @@ func ToString(res helpers.Response, opts cli.Options) string {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Display.Statistics {
|
||||
s += "\n;; Query time: " + res.RTT.String()
|
||||
// Add extra information to server string
|
||||
var extra string
|
||||
|
||||
switch {
|
||||
case opts.TCP:
|
||||
extra = ":" + strconv.Itoa(opts.Port) + " (TCP)"
|
||||
|
@ -138,14 +155,15 @@ func ToString(res helpers.Response, opts cli.Options) string {
|
|||
for i, resp := range res.DNS.Answer {
|
||||
temp := strings.Split(resp.String(), "\t")
|
||||
s += temp[len(temp)-1]
|
||||
|
||||
if opts.Identify {
|
||||
s += " from server " + opts.Request.Server + " in " + res.RTT.String()
|
||||
}
|
||||
|
||||
// Don't print newline on last line
|
||||
if i != len(res.DNS.Answer)-1 {
|
||||
s += "\n"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
// TODO
|
||||
func TestToString(t *testing.T) {
|
||||
assert.Assert(t, 1 == 1+0)
|
||||
}
|
|
@ -1,8 +1,4 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base",
|
||||
":npm",
|
||||
":gomod"
|
||||
]
|
||||
"extends": ["config:base", ":npm", ":gomod"]
|
||||
}
|
||||
|
|
32
template.mk
32
template.mk
|
@ -1,13 +1,18 @@
|
|||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
# Template for the BSD/GNU makefiles
|
||||
|
||||
HASH ?= `git describe --always --dirty || echo "UNKNOWN"`
|
||||
VER ?= "git-$(HASH)"
|
||||
HASH ?= `git describe --always --dirty --broken || echo "UNKNOWN"`
|
||||
VERSION ?= "git-$(HASH)"
|
||||
|
||||
SOURCES ?= $(shell find . -name "*.go" -type f ! -name '*_test*')
|
||||
TEST_SOURCES ?= $(shell find . -name "*_test.go" -type f)
|
||||
|
||||
CGO_ENABLED ?= 0
|
||||
GO ?= go
|
||||
TEST ?= $(GO) test -v -race
|
||||
COVER ?= $(GO) tool cover
|
||||
GOFLAGS ?= -ldflags "-s -w -X=main.version=$(VER)" -trimpath
|
||||
GOFLAGS ?= -ldflags "-s -w -X=main.version=$(VERSION)" -trimpath
|
||||
DESTDIR :=
|
||||
|
||||
PREFIX ?= /usr/local
|
||||
BIN ?= bin
|
||||
|
@ -20,15 +25,25 @@ PROG ?= awl
|
|||
# hehe
|
||||
all: $(PROG) doc/$(PROG).1
|
||||
|
||||
doc/$(PROG).1: doc/wiki/$(PROG).1.md
|
||||
@cp doc/awl.1 doc/awl.bak
|
||||
$(SCDOC) <doc/wiki/$(PROG).1.md >doc/$(PROG).1 && rm doc/awl.bak || mv doc/awl.bak doc/awl.1
|
||||
doc/$(PROG).1: doc/$(PROG).1.scd
|
||||
$(SCDOC) <doc/$(PROG).1.scd >doc/$(PROG).1
|
||||
|
||||
|
||||
## test: run go test
|
||||
test:
|
||||
test: $(TEST_SOURCES)
|
||||
$(GO) test -cover -coverprofile=coverage/coverage.out ./...
|
||||
|
||||
test-ci:
|
||||
$(TEST)
|
||||
|
||||
## fuzz: runs fuzz tests
|
||||
fuzz:
|
||||
cd cli
|
||||
$(TEST) -fuzz=FuzzFlags -fuzztime 10000x
|
||||
$(TEST) -fuzz=FuzzDig -fuzztime 10000x
|
||||
$(TEST) -fuzz=FuzzParseArgs -fuzztime 10000x
|
||||
cd ..
|
||||
|
||||
coverage/coverage.out: test
|
||||
$(COVER) -func=coverage/coverage.out
|
||||
$(COVER) -html=coverage/coverage.out -o coverage/cover.html
|
||||
|
@ -44,11 +59,12 @@ vet:
|
|||
|
||||
## lint: lint awl, using fmt, vet and golangci-lint
|
||||
lint: fmt vet
|
||||
-golangci-lint run --fix
|
||||
golangci-lint run --fix
|
||||
|
||||
## clean: clean the build files
|
||||
clean:
|
||||
$(GO) clean
|
||||
rm doc/$(PROG).1
|
||||
|
||||
## help: Prints this help message
|
||||
help:
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
// Helper functions
|
||||
// Package util contains helper functions that don't belong anywhere else
|
||||
package util
|
||||
|
|
|
@ -4,11 +4,11 @@ package util
|
|||
|
||||
import "git.froth.zone/sam/awl/logawl"
|
||||
|
||||
// Initialize the logawl instance.
|
||||
func InitLogger(verbosity int) (logger *logawl.Logger) {
|
||||
logger = logawl.New()
|
||||
// InitLogger initializes the logawl instance.
|
||||
func InitLogger(verbosity int) (log *logawl.Logger) {
|
||||
log = logawl.New()
|
||||
|
||||
logger.SetLevel(logawl.Level(verbosity))
|
||||
log.SetLevel(logawl.Level(verbosity))
|
||||
|
||||
return logger
|
||||
return log
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
func TestInitLogger(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
logger := util.InitLogger(0)
|
||||
assert.Equal(t, logger.Level, logawl.Level(0))
|
||||
}
|
||||
|
|
113
util/options.go
Normal file
113
util/options.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"git.froth.zone/sam/awl/logawl"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Options is the grand CLI options structure.
|
||||
type Options struct {
|
||||
Logger *logawl.Logger
|
||||
TLSHost string
|
||||
EDNS
|
||||
Request Request
|
||||
Port int
|
||||
Verbosity int
|
||||
Display Displays
|
||||
TC bool
|
||||
ShowTTL bool
|
||||
ShowQuery bool
|
||||
AA bool
|
||||
AD bool
|
||||
CD bool
|
||||
QR bool
|
||||
RD bool
|
||||
RA bool
|
||||
IPv4 bool
|
||||
Z bool
|
||||
Reverse bool
|
||||
HumanTTL bool
|
||||
Truncate bool
|
||||
Short bool
|
||||
Identify bool
|
||||
JSON bool
|
||||
XML bool
|
||||
YAML bool
|
||||
QUIC bool
|
||||
HTTPS bool
|
||||
TLSNoVerify bool
|
||||
TLS bool
|
||||
DNSCrypt bool
|
||||
TCP bool
|
||||
IPv6 bool
|
||||
}
|
||||
|
||||
// Displays contains toggles for what to (and not to) display.
|
||||
type Displays struct {
|
||||
Comments bool
|
||||
Question bool // QUESTION SECTION
|
||||
Opt bool // OPT PSEUDOSECTION
|
||||
Answer bool // ANSWER SECTION
|
||||
Authority bool // AUTHORITY SECTION
|
||||
Additional bool // ADDITIONAL SECTION
|
||||
Statistics bool // Query time, message size, etc.
|
||||
UcodeTranslate bool // Translate Punycode back to Unicode
|
||||
}
|
||||
|
||||
// EDNS contains toggles for various EDNS options.
|
||||
type EDNS struct {
|
||||
Subnet dns.EDNS0_SUBNET
|
||||
ZFlag uint16
|
||||
BufSize uint16
|
||||
EnableEDNS bool
|
||||
Cookie bool
|
||||
DNSSEC bool
|
||||
Expire bool
|
||||
KeepOpen bool
|
||||
Nsid bool
|
||||
Padding bool
|
||||
Version uint8
|
||||
}
|
||||
|
||||
// ParseSubnet takes a subnet argument and makes it into one that the DNS library
|
||||
// understands.
|
||||
func ParseSubnet(subnet string, opts *Options) error {
|
||||
ip, inet, err := net.ParseCIDR(subnet)
|
||||
if err != nil {
|
||||
// TODO: make not a default?
|
||||
if subnet == "0" {
|
||||
opts.EDNS.Subnet = dns.EDNS0_SUBNET{
|
||||
Code: dns.EDNS0SUBNET,
|
||||
Family: 1,
|
||||
SourceNetmask: 0,
|
||||
SourceScope: 0,
|
||||
Address: net.IPv4(0, 0, 0, 0),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("EDNS subnet parsing: %w", err)
|
||||
}
|
||||
|
||||
sub, _ := inet.Mask.Size()
|
||||
opts.EDNS.Subnet = dns.EDNS0_SUBNET{}
|
||||
opts.EDNS.Subnet.Address = ip
|
||||
opts.EDNS.Subnet.SourceNetmask = uint8(sub)
|
||||
|
||||
switch ip.To4() {
|
||||
case nil:
|
||||
// Not a valid IPv4 so assume IPv6
|
||||
opts.EDNS.Subnet.Family = 2
|
||||
default:
|
||||
// Valid IPv4
|
||||
opts.EDNS.Subnet.Family = 1
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
37
util/options_test.go
Normal file
37
util/options_test.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package util_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.froth.zone/sam/awl/util"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestSubnet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
subnet := []string{
|
||||
"0.0.0.0/0",
|
||||
"::0/0",
|
||||
"0",
|
||||
"127.0.0.1/32",
|
||||
}
|
||||
|
||||
for _, test := range subnet {
|
||||
test := test
|
||||
t.Run(test, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := util.ParseSubnet(test, new(util.Options))
|
||||
assert.NilError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidSub(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := util.ParseSubnet("1", new(util.Options))
|
||||
assert.ErrorContains(t, err, "invalid CIDR address")
|
||||
}
|
25
util/query.go
Normal file
25
util/query.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// Response is 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
|
||||
}
|
||||
|
||||
// Request is a structure for a DNS query.
|
||||
type Request struct {
|
||||
Server string `json:"server"`
|
||||
Name string `json:"name"`
|
||||
Timeout time.Duration
|
||||
Retries int
|
||||
Type uint16 `json:"request"`
|
||||
Class uint16 `json:"class"`
|
||||
}
|
|
@ -17,14 +17,15 @@ func (e *errReverseDNS) Error() string {
|
|||
return fmt.Sprintf("reverseDNS: invalid value %s given", e.addr)
|
||||
}
|
||||
|
||||
// Given an IP or phone number, return a canonical string to be queried.
|
||||
// ReverseDNS is given an IP or phone number and returns a canonical string to be queried.
|
||||
func ReverseDNS(address string, querInt uint16) (string, error) {
|
||||
query := dns.TypeToString[querInt]
|
||||
if query == "PTR" {
|
||||
str, err := dns.ReverseAddr(address)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not reverse: %w", err)
|
||||
return "", fmt.Errorf("PTR reverse: %w", err)
|
||||
}
|
||||
|
||||
return str, nil
|
||||
} else if query == "NAPTR" {
|
||||
// get rid of characters not needed
|
||||
|
@ -38,6 +39,7 @@ func ReverseDNS(address string, querInt uint16) (string, error) {
|
|||
fmt.Fprintf(&arpa, "%c.", c)
|
||||
}
|
||||
arpa.WriteString("e164.arpa.")
|
||||
|
||||
return arpa.String(), nil
|
||||
}
|
||||
|
||||
|
@ -50,5 +52,6 @@ func reverse(s string) string {
|
|||
for i, j := 0, len(rns)-1; i < j; i, j = i+1, j-1 {
|
||||
rns[i], rns[j] = rns[j], rns[i]
|
||||
}
|
||||
|
||||
return string(rns)
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ var (
|
|||
|
||||
func TestIPv4(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
act, err := util.ReverseDNS("8.8.4.4", PTR)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, act, "4.4.8.8.in-addr.arpa.", "IPv4 reverse")
|
||||
|
@ -24,6 +25,7 @@ func TestIPv4(t *testing.T) {
|
|||
|
||||
func TestIPv6(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
act, err := util.ReverseDNS("2606:4700:4700::1111", PTR)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, act, "1.1.1.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.7.4.0.0.7.4.6.0.6.2.ip6.arpa.", "IPv6 reverse")
|
||||
|
@ -31,6 +33,7 @@ func TestIPv6(t *testing.T) {
|
|||
|
||||
func TestNAPTR(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
in string
|
||||
want string
|
||||
|
@ -54,12 +57,14 @@ func TestNAPTR(t *testing.T) {
|
|||
|
||||
func TestInvalid(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := util.ReverseDNS("AAAAA", 1)
|
||||
assert.ErrorContains(t, err, "invalid value AAAAA given")
|
||||
}
|
||||
|
||||
func TestInvalid2(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := util.ReverseDNS("1.0", PTR)
|
||||
assert.ErrorContains(t, err, "could not reverse")
|
||||
assert.ErrorContains(t, err, "PTR reverse")
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue