(feat)EDNS (#55)
continuous-integration/drone/push Build is passing Details

Reviewed-on: #55
This commit is contained in:
Sam Therapy 2022-08-04 01:09:49 +00:00
parent b7a563a989
commit 07728cffdb
51 changed files with 1552 additions and 387 deletions

View File

@ -2,6 +2,7 @@
local testing(version, arch) = {
kind: "pipeline",
type: "docker",
name: version + "-" + arch ,
platform: {
arch: arch
@ -16,19 +17,27 @@ local testing(version, arch) = {
},
{
name: "lint",
image: "rancher/drone-golangci-lint:latest"
image: "rancher/drone-golangci-lint:latest",
depends_on: [
"submodules",
],
},
{
name: "test",
image: "golang:" + version,
commands: [
"go test -race ./... -cover"
]
"go test -v -race ./... -cover"
],
depends_on: [
"submodules",
],
},
],
trigger: {
trigger: {
event: {
exclude: "tag",
exclude: [
"tag"
],
}
},
};
@ -36,14 +45,17 @@ local testing(version, arch) = {
// "Inspired by" https://goreleaser.com/ci/drone/
local release() = {
kind: "pipeline",
type: "docker",
name: "release",
trigger: {
event: "tag"
event: [
"tag"
],
},
steps: [
{
name: "fetch",
image: "docker:git",
image: "alpine/git",
commands : [
"git fetch --tags",
"git submodule update --init --recursive"
@ -67,15 +79,16 @@ local release() = {
commands: [
"goreleaser release"
],
// when: {
// event: "tag"
// }
}
]
};
[
testing("1.19", "amd64"),
testing("1.19", "arm64"),
testing("1.18", "amd64"),
testing("1.18", "arm64"),
release()
]

28
.github/ISSUE_TEMPLATE/bug.md vendored Normal file
View File

@ -0,0 +1,28 @@
---
name: Bug report
about: Report a bug
---
**Describe the bug**
A clear and concise description of what the bug is.
**Reproduction steps**
Steps to reproduce the behavior:
1. 1
2. 2
3. Bug
**Expected behaviour**
A clear and concise description of what you expected to happen.
**Screenshots / Logs**
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]
**Additional context**
Add any other context about the problem here.

18
.github/ISSUE_TEMPLATE/feature.md vendored Normal file
View File

@ -0,0 +1,18 @@
---
name: Feature request
about: Suggest a feature
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is.
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
Links to implementations in dig, drill, etc. should go here.
**Additional context**
Add any other context or screenshots about the feature request here.

9
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,9 @@
<!--
Please check the following:
1. Make sure you are targeting the `master` branch.
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)
-->

4
.gitignore vendored
View File

@ -21,3 +21,7 @@ dist/
# Test coverage
coverage/*
!coverage/.gitkeep
awl
.dccache

View File

@ -12,11 +12,13 @@ endif
$(PROG):
$(GO) build -o $(EXE) $(GOFLAGS) .
## install: installs awl
install: all
ifeq ($(OS),Windows_NT)
## install: installs awl
install:
$(GO) install $(GOFLAGS) .
else
install: all
install -m755 $(PROG) $(PREFIX)/$(BIN)
install -m644 doc/$(PROG).1 $(MAN)/man1
endif

View File

View File

@ -6,7 +6,7 @@ include template.mk
$(PROG):
$(GO) build -o $(PROG) $(GOFLAGS) .
## install: installs awl
## install: installs awl and the manpage, RUN AS ROOT
install: all
install -m755 $(PROG) $(PREFIX)/$(BIN)
install -m644 doc/$(PROG).1 $(MAN)/man1

View File

@ -61,6 +61,6 @@ emails? Send a patch to the
Found a bug or want a new feature? Create an issue
[here](https://git.froth.zone/sam/awl/issues).
### License
### Licence
See [LICENSE](./LICENSE)
See [LICENCE](./LICENCE)

View File

@ -6,17 +6,18 @@ import (
"fmt"
"os"
"runtime"
"strconv"
"strings"
"time"
"git.froth.zone/sam/awl/internal/helpers"
"git.froth.zone/sam/awl/util"
"github.com/miekg/dns"
flag "github.com/stefansundin/go-zflag"
)
// Parse the arguments passed into awl.
// ParseCLI parses arguments given from the CLI and passes them into an `Options`
// struct.
func ParseCLI(version string) (Options, error) {
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
@ -45,10 +46,22 @@ func ParseCLI(version string) (Options, error) {
ipv6 = flag.Bool("6", false, "force IPv6", flag.OptShorthand('6'))
reverse = flag.Bool("reverse", false, "do a reverse lookup", flag.OptShorthand('x'))
timeout = flag.Float32("timeout", 1, "Timeout, in `seconds`")
retry = flag.Int("retries", 2, "number of `times` to retry")
dnssec = flag.Bool("dnssec", false, "enable DNSSEC", flag.OptShorthand('D'))
truncate = flag.Bool("no-truncate", false, "ignore truncation if a UDP request truncates (default= retry with TCP)")
timeout = flag.Float32("timeout", 1, "Timeout, in `seconds`")
retry = flag.Int("retries", 2, "number of `times` to retry")
edns = flag.Bool("no-edns", false, "disable EDNS entirely")
ednsVer = flag.Uint8("edns-ver", 0, "set EDNS version")
expire = flag.Bool("expire", false, "set EDNS expire")
dnssec = flag.Bool("dnssec", false, "enable DNSSEC", flag.OptShorthand('D'))
nsid = flag.Bool("nsid", false, "set EDNS NSID", flag.OptShorthand('n'))
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")
subnet = flag.String("subnet", "", "set EDNS subnet")
padding = flag.Bool("pad", false, "set EDNS padding")
truncate = flag.Bool("no-truncate", false, "ignore truncation if a UDP request truncates (default: retry with TCP)")
tcp = flag.Bool("tcp", false, "use TCP")
dnscrypt = flag.Bool("dnscrypt", false, "use DNSCrypt")
@ -65,18 +78,20 @@ func ParseCLI(version string) (Options, error) {
tc = flag.Bool("tc", false, "set/unset TC (TrunCated) flag (default: not set)")
z = flag.Bool("z", false, "set/unset Z (Zero) flag (default: not set)", flag.OptShorthand('z'))
short = flag.Bool("short", false, "print just the results, equivalent to dig +short", flag.OptShorthand('s'))
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'))
xml = flag.Bool("xml", false, "print the result(s) as XML", flag.OptShorthand('X'))
yaml = flag.Bool("yaml", false, "print the result(s) as yaml", flag.OptShorthand('y'))
noC = flag.Bool("no-comments", false, "disable printing the comments")
noQ = flag.Bool("no-question", false, "disable printing the question section")
noOpt = flag.Bool("no-opt", false, "disable printing the OPT pseudosection")
noAns = flag.Bool("no-answer", false, "disable printing the answer section")
noAuth = flag.Bool("no-authority", false, "disable printing the authority section")
noAdd = flag.Bool("no-additional", false, "disable printing the additonal section")
noAdd = flag.Bool("no-additional", false, "disable printing the additional section")
noStats = flag.Bool("no-statistics", false, "disable printing the statistics section")
verbosity = flag.Int("verbosity", 0, "sets verbosity `level`", flag.OptShorthand('v'), flag.OptNoOptDefVal("2"))
verbosity = flag.Int("verbosity", 1, "sets verbosity `level`", flag.OptShorthand('v'), flag.OptNoOptDefVal("2"))
versionFlag = flag.Bool("version", false, "print version information", flag.OptShorthand('V'))
)
@ -86,34 +101,42 @@ func ParseCLI(version string) (Options, error) {
// Parse the flags
err := flag.CommandLine.Parse(os.Args[1:])
if err != nil {
return Options{Logger: util.InitLogger(*verbosity)}, err
return Options{Logger: util.InitLogger(*verbosity)}, fmt.Errorf("flag parse error: %w", err)
}
// TODO: DRY, dumb dumb.
mbz, err := strconv.ParseInt(*zflag, 0, 16)
if err != nil {
return Options{Logger: util.InitLogger(*verbosity)}, fmt.Errorf("EDNS MBZ error: %w", err)
}
opts := Options{
Logger: util.InitLogger(*verbosity),
Port: *port,
IPv4: *ipv4,
IPv6: *ipv6,
DNSSEC: *dnssec,
Short: *short,
TCP: *tcp,
DNSCrypt: *dnscrypt,
TLS: *tls,
HTTPS: *https,
QUIC: *quic,
Truncate: *truncate,
AA: *aa,
AD: *ad,
TC: *tc,
Z: *z,
CD: *cd,
QR: *qr,
RD: *rd,
RA: *ra,
Reverse: *reverse,
JSON: *json,
XML: *xml,
YAML: *yaml,
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{
Type: dns.StringToType[strings.ToUpper(*qType)],
Class: dns.StringToClass[strings.ToUpper(*class)],
@ -122,12 +145,34 @@ func ParseCLI(version string) (Options, error) {
Retries: *retry,
},
Display: Displays{
Comments: !*noC,
Question: !*noQ,
Opt: !*noOpt,
Answer: !*noAns,
Authority: !*noAuth,
Additional: !*noAdd,
Statistics: !*noStats,
},
EDNS: EDNS{
EnableEDNS: !*edns,
Cookie: !*cookie,
DNSSEC: *dnssec,
BufSize: *udpBufSize,
Version: *ednsVer,
Expire: *expire,
KeepOpen: *tcpKeepAlive,
Nsid: *nsid,
ZFlag: uint16(mbz & 0x7FFF),
Padding: *padding,
},
}
// TODO: DRY
if *subnet != "" {
err := parseSubnet(*subnet, &opts)
if err != nil {
return opts, err
}
}
opts.Logger.Info("POSIX flags parsed")
@ -142,7 +187,6 @@ func ParseCLI(version string) (Options, error) {
// This includes the dig-style (+) options
err = ParseMiscArgs(flag.Args(), &opts)
if err != nil {
opts.Logger.Warn(err)
return opts, err
}
opts.Logger.Info("Dig/Drill flags parsed")

View File

@ -11,25 +11,49 @@ 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.Assert(t, (opts.Port == 53))
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.Assert(t, (opts.Port == 853))
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 = []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
}
// nolint: paralleltest
func TestInvalidFlag(t *testing.T) {
old := os.Args
os.Args = []string{"awl", "--treebug"}
@ -38,14 +62,16 @@ func TestInvalidFlag(t *testing.T) {
os.Args = old
}
// nolint: paralleltest
func TestInvalidDig(t *testing.T) {
old := os.Args
os.Args = []string{"awl", "+a"}
_, err := cli.ParseCLI("TEST")
assert.ErrorContains(t, err, "dig: unknown flag")
assert.ErrorContains(t, err, "digflags: invalid argument")
os.Args = old
}
// nolint: paralleltest
func TestVersion(t *testing.T) {
old := os.Args
os.Args = []string{"awl", "--version"}
@ -54,6 +80,7 @@ func TestVersion(t *testing.T) {
os.Args = old
}
// nolint: paralleltest
func TestTimeout(t *testing.T) {
args := [][]string{
{"awl", "+timeout=0"},
@ -69,6 +96,7 @@ func TestTimeout(t *testing.T) {
}
}
// nolint: paralleltest
func TestRetries(t *testing.T) {
args := [][]string{
{"awl", "+retry=-2"},
@ -85,6 +113,7 @@ func TestRetries(t *testing.T) {
}
}
// nolint: paralleltest
func FuzzFlags(f *testing.F) {
testcases := []string{"git.froth.zone", "", "!12345", "google.com.edu.org.fr"}
for _, tc := range testcases {
@ -92,7 +121,7 @@ func FuzzFlags(f *testing.F) {
}
f.Fuzz(func(t *testing.T, orig string) {
os.Args = []string{orig}
os.Args = []string{"awl", orig}
//nolint:errcheck // Only make sure the program does not crash
cli.ParseCLI("TEST")
})

View File

@ -9,108 +9,193 @@ import (
"time"
)
// Parse dig-like commands and set the options as such.
// 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 {
// 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 {
// Set DNS query flags
case "aa", "aaflag", "aaonly", "noaa", "noaaflag", "noaaonly":
case "aa", "aaflag", "aaonly":
opts.AA = isNo
case "ad", "adflag", "noad", "noadflag":
case "ad", "adflag":
opts.AD = isNo
case "cd", "cdflag", "nocd", "nocdflag":
case "cd", "cdflag":
opts.CD = isNo
case "qr", "qrflag", "noqr", "noqrflag":
case "qrflag":
opts.QR = isNo
case "ra", "raflag", "nora", "noraflag":
case "ra", "raflag":
opts.RA = isNo
case "rd", "rdflag", "recurse", "nord", "nordflag", "norecurse":
case "rd", "rdflag", "recurse":
opts.RD = isNo
case "tc", "tcflag", "notc", "notcflag":
case "tc", "tcflag":
opts.TC = isNo
case "z", "zflag", "noz", "nozflag":
case "z", "zflag":
opts.Z = isNo
// End DNS query flags
case "qr":
opts.ShowQuery = isNo
case "ttlunits":
opts.HumanTTL = isNo
case "ttlid":
opts.ShowTTL = isNo
// EDNS queries
case "dnssec":
opts.EDNS.DNSSEC = isNo
case "expire":
opts.EDNS.Expire = isNo
case "cookie":
opts.EDNS.Cookie = isNo
case "keepopen", "keepalive":
opts.EDNS.KeepOpen = isNo
case "nsid":
opts.EDNS.Nsid = isNo
case "padding":
opts.EDNS.Padding = isNo
// End EDNS queries
// DNS-over-X
case "dnssec", "nodnssec":
opts.DNSSEC = isNo
case "tcp", "vc", "notcp", "novc":
case "tcp", "vc":
opts.TCP = isNo
case "ignore", "noignore":
case "ignore":
opts.Truncate = isNo
case "tls", "notls":
case "tls":
opts.TLS = isNo
case "dnscrypt", "nodnscrypt":
case "dnscrypt":
opts.DNSCrypt = isNo
case "https", "nohttps":
case "https":
opts.HTTPS = isNo
case "quic", "noquic":
case "quic":
opts.QUIC = isNo
// End DNS-over-X
// Formatting
case "short", "noshort":
case "short":
opts.Short = isNo
case "json", "nojson":
case "identify":
opts.Identify = isNo
case "json":
opts.JSON = isNo
case "xml", "noxml":
case "xml":
opts.XML = isNo
case "yaml", "noyaml":
case "yaml":
opts.YAML = isNo
// End formatting
// Output
// TODO: get this to work
// case "comments", "nocomments":
// opts.Display.Comments = isNo
case "question", "noquestion":
case "comments":
opts.Display.Comments = isNo
case "question":
opts.Display.Question = isNo
case "answer", "noanswer":
case "opt":
opts.Display.Opt = isNo
case "answer":
opts.Display.Answer = isNo
case "authority", "noauthority":
case "authority":
opts.Display.Authority = isNo
case "additional", "noadditional":
case "additional":
opts.Display.Additional = isNo
case "stats", "nostats":
case "stats":
opts.Display.Statistics = isNo
case "all", "noall":
case "all":
opts.Display.Comments = isNo
opts.Display.Question = isNo
opts.Display.Opt = isNo
opts.Display.Answer = isNo
opts.Display.Authority = isNo
opts.Display.Additional = isNo
opts.Display.Statistics = isNo
case "idnout":
opts.Display.UcodeTranslate = isNo
default:
// Recursive switch statements WOO
switch {
case strings.HasPrefix(arg, "timeout"):
timeout, err := strconv.Atoi(strings.Split(arg, "=")[1])
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)
}
if err != nil {
return fmt.Errorf("dig: Invalid timeout value")
opts.Request.Timeout = time.Duration(timeout)
} else {
return fmt.Errorf("digflags: Invalid timeout value: %w", errNoArg)
}
opts.Request.Timeout = time.Duration(timeout)
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
case strings.HasPrefix(arg, "retry"), strings.HasPrefix(arg, "tries"):
tries, err := strconv.Atoi(strings.Split(arg, "=")[1])
if err != nil {
return fmt.Errorf("dig: Invalid retry value")
// 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)
}
if strings.HasPrefix(arg, "tries") {
tries++
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)
}
opts.Request.Retries = tries
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 fmt.Errorf("dig: unknown flag %s given", arg)
return &errInvalidArg{arg[0]}
}
}
return nil

View File

@ -21,7 +21,22 @@ func FuzzDig(f *testing.F) {
"rdflag", "recurse", "nordflag", "norecurse",
"tcflag", "notcflag",
"zflag", "nozflag",
"qr", "noqr",
"ttlunits", "nottlunits",
"ttlid", "nottlid",
"dnssec", "nodnssec",
"edns", "edns=a", "edns=0", "noedns",
"expire", "noexpire",
"ednsflags", "ednsflags=\"", "ednsflags=1", "noednsflags",
"subnet=0.0.0.0/0", "subnet=::0/0", "subnet=b", "subnet=0", "subnet",
"cookie", "nocookeie",
"keepopen", "keepalive", "nokeepopen", "nokeepalive",
"nsid", "nonsid",
"padding", "nopadding",
"bufsize=512", "bufsize=a", "bufsize",
"time=5", "timeout=a", "timeout",
"retry=a", "retry=3", "retry",
"tries=2", "tries=b", "tries",
"tcp", "vc", "notcp", "novc",
"ignore", "noignore",
"tls", "notls",
@ -29,15 +44,19 @@ func FuzzDig(f *testing.F) {
"https", "nohttps",
"quic", "noquic",
"short", "noshort",
"identify", "noidentify",
"json", "nojson",
"xml", "noxml",
"yaml", "noyaml",
"comments", "nocomments",
"question", "noquestion",
"opt", "noopt",
"answer", "noanswer",
"authority", "noauthority",
"additional", "noadditional",
"stats", "nostats",
"all", "noall",
"idnout", "noidnout",
"invalid",
}
for _, tc := range seeds {
@ -49,7 +68,7 @@ func FuzzDig(f *testing.F) {
opts.Logger = util.InitLogger(0)
err := cli.ParseDig(orig, opts)
if err != nil {
assert.ErrorContains(t, err, "unknown flag")
assert.ErrorContains(t, err, "digflags:")
}
})
}

View File

@ -9,12 +9,12 @@ import (
"git.froth.zone/sam/awl/conf"
"git.froth.zone/sam/awl/util"
"github.com/miekg/dns"
"golang.org/x/net/idna"
)
// Parse the wildcard arguments, drill style.
// 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 {
var err error
@ -46,28 +46,34 @@ func ParseMiscArgs(args []string, opts *Options) error {
default:
opts.Request.Server = arg
}
case strings.Contains(arg, "."):
opts.Logger.Info(arg, "detected as a domain name")
opts.Request.Name, err = idna.ToASCII(arg)
if err != nil {
return err
}
case ok:
opts.Logger.Info(arg, "detected as a type")
// If it's a DNS request, it's a DNS request (obviously)
opts.Request.Type = r
// Dig-style +queries
case strings.HasPrefix(arg, "+"):
opts.Logger.Info(arg, "detected as a dig query")
// Dig-style +queries
err = ParseDig(strings.ToLower(arg[1:]), opts)
if err != nil {
return err
}
// Domain names
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)
}
// DNS query type
case ok:
opts.Logger.Info(arg, "detected as a type")
opts.Request.Type = r
// Domain?
default:
opts.Logger.Info(arg, "is unknown. Assuming domain")
opts.Request.Name, err = idna.ToASCII(arg)
if err != nil {
return err
return fmt.Errorf("punycode translate error: %w", err)
}
}
}
@ -107,21 +113,23 @@ func ParseMiscArgs(args []string, opts *Options) error {
opts.Request.Server = "95.216.99.249"
} else {
// Make sure that if IPv4 or IPv6 is asked for it actually uses it
harmful:
for _, srv := range resolv.Servers {
if opts.IPv4 {
switch {
case opts.IPv4:
if strings.Contains(srv, ".") {
opts.Request.Server = srv
break
break harmful
}
} else if opts.IPv6 {
case opts.IPv6:
if strings.Contains(srv, ":") {
opts.Request.Server = srv
break
break harmful
}
} else {
default:
//#nosec -- This isn't used for anything secure
opts.Request.Server = resolv.Servers[rand.Intn(len(resolv.Servers))]
break
break harmful
}
}
}
@ -137,7 +145,7 @@ func ParseMiscArgs(args []string, opts *Options) error {
}
opts.Request.Name, err = util.ReverseDNS(opts.Request.Name, opts.Request.Type)
if err != nil {
return err
return fmt.Errorf("reverse DNS error: %w", err)
}
}

View File

@ -8,7 +8,6 @@ import (
"git.froth.zone/sam/awl/cli"
"git.froth.zone/sam/awl/util"
"github.com/miekg/dns"
"gotest.tools/v3/assert"
)
@ -139,7 +138,6 @@ func TestFlagSetting(t *testing.T) {
assert.Assert(t, opts.HTTPS)
case 3:
assert.Assert(t, opts.QUIC)
}
})
}

View File

@ -4,9 +4,12 @@ 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.
@ -15,13 +18,13 @@ type Options struct {
Port int // DNS port
IPv4 bool // Force IPv4
IPv6 bool // Force IPv6
DNSSEC bool // Enable DNSSEC
TCP bool // Query with TCP
DNSCrypt bool // Query over DNSCrypt
TLS bool // Query over TLS
HTTPS bool // Query over HTTPS
QUIC bool // Query over QUIC
Truncate bool // Ignore truncation
ShowQuery bool // Show query before being sent
AA bool // Set Authoratative Answer
AD bool // Set Authenticated Data
CD bool // Set CD
@ -32,24 +35,87 @@ type Options struct {
Z bool // Set Z (Zero)
Reverse bool // Make reverse query
Verbosity int // Set logawl verbosity
// HumanTTL bool // Make TTL human readable
Short bool // Short output
JSON bool // Outout as JSON
XML bool // Output as XML
YAML bool // Output at YAML
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
// What to (and not to) display.
type Displays struct {
// Comments bool
Question bool // QUESTION SECTION
Answer bool // ANSWER SECTION
Authority bool // AUTHORITY SECTION
Additional bool // ADDITIONAL SECTION
Statistics bool // Query time, message size, etc.
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)
}

View File

@ -3,7 +3,7 @@
package conf
import (
"fmt"
"errors"
"strings"
"github.com/miekg/dns"
@ -23,7 +23,7 @@ func GetPlan9Config(str string) (*dns.ClientConfig, error) {
}
}
if len(servers) == 0 {
return nil, fmt.Errorf("plan9: no DNS servers found")
return nil, errPlan9
}
// TODO: read more about how customizable Plan 9 is
@ -38,3 +38,5 @@ func GetPlan9Config(str string) (*dns.ClientConfig, error) {
func splitChars(r rune) bool {
return r == ' ' || r == '\t'
}
var errPlan9 = errors.New("plan9Config: no DNS servers found")

View File

@ -6,7 +6,6 @@ import (
"testing"
"git.froth.zone/sam/awl/conf"
"gotest.tools/v3/assert"
)

View File

@ -4,6 +4,7 @@
package conf
import (
"fmt"
"os"
"runtime"
@ -15,7 +16,7 @@ func GetDNSConfig() (*dns.ClientConfig, error) {
if runtime.GOOS == "plan9" {
dat, err := os.ReadFile("/net/ndb")
if err != nil {
return nil, err
return nil, fmt.Errorf("plan9 ndb: %w", err)
}
return GetPlan9Config(string(dat))
} else {

View File

@ -13,7 +13,7 @@ import (
func TestNonWinConfig(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Not running Windows, skipping")
t.Skip("Running Windows, skipping")
}
conf, err := conf.GetDNSConfig()
assert.NilError(t, err)

View File

@ -4,6 +4,7 @@
package conf
import (
"fmt"
"strings"
"unsafe"
@ -23,7 +24,7 @@ func GetDNSConfig() (*dns.ClientConfig, error) {
// 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 {
return nil, err
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 {

View File

@ -5,7 +5,7 @@
.nh
.ad l
.\" Begin generated content:
.TH "awl" "1" "2022-07-25"
.TH "awl" "1" "2022-08-03"
.PP
.SH NAME
awl - DNS lookup tool

View File

@ -1,9 +1,8 @@
/*
awl is a DNS lookup tool written in Go,
similar to (and heavily inspired by) drill.
awl is a DNS lookup tool written in Go, similar to (and heavily inspired by) drill.
It runs and displays similar outputs to drill, without any frills.
Options are given to print with JSON
Options are given to print with JSON, XML and YAML.
Supports results from DNS-over-[UDP, TCP, TLS, HTTPS, QUIC] servers

7
go.mod
View File

@ -4,11 +4,12 @@ go 1.18
require (
github.com/ameshkov/dnscrypt/v2 v2.2.3
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5
github.com/lucas-clemente/quic-go v0.28.1
github.com/miekg/dns v1.1.50
github.com/stefansundin/go-zflag v1.1.1
golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.3.0
)
@ -33,8 +34,8 @@ require (
github.com/stretchr/testify v1.8.0 // indirect
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/sys v0.0.0-20220803195053-6e608f9ce704
golang.org/x/sys v0.0.0-20220731174439-a90be440212d
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.11 // indirect
golang.org/x/tools v0.1.12 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
)

34
go.sum
View File

@ -31,6 +31,8 @@ github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0JFybxFKZ2WMLabgx3uXnd7rpGs=
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
@ -217,22 +219,6 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220725212005-46097bf591d3 h1:2yWTtPWWRcISTw3/o+s/Y4UOMnQL71DWyToOANFusCg=
golang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM=
golang.org/x/net v0.0.0-20220726230323-06994584191e h1:wOQNKh1uuDGRnmgF0jDxh7ctgGy/3P4rYWQRVJD4/Yg=
golang.org/x/net v0.0.0-20220726230323-06994584191e/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM=
golang.org/x/net v0.0.0-20220728012108-993b7b1e3a27 h1:Khs7GS6mUxEA1e5DfKm9ojYX4BiI297wdliOwp/CPmw=
golang.org/x/net v0.0.0-20220728012108-993b7b1e3a27/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20220728030405-41545e8bf201 h1:bvOltf3SADAfG05iRml8lAB3qjoEX5RCyN4K6G5v3N0=
golang.org/x/net v0.0.0-20220728030405-41545e8bf201/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20220728153142-1f511ac62c11 h1:BZ+7NKCw5UIzmPP4GFz9VcjTVpN9mswsWRDmJ2tWKAs=
golang.org/x/net v0.0.0-20220728153142-1f511ac62c11/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20220728181054-f92ba40d432d h1:3iMzhioG3w6/URLOo7X7eZRkWoLdz9iWE/UsnXHNTfY=
golang.org/x/net v0.0.0-20220728181054-f92ba40d432d/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20220728211354-c7608f3a8462 h1:UreQrH7DbFXSi9ZFox6FNT3WBooWmdANpU+IfkT1T4I=
golang.org/x/net v0.0.0-20220728211354-c7608f3a8462/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b h1:3ogNYyK4oIQdIKzTu68hQrr4iuVxF3AxKl9Aj/eDrw0=
golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -246,8 +232,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -273,18 +259,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220727055044-e65921a090b8 h1:dyU22nBWzrmTQxtNrr4dzVOvaw35nUYE279vF9UmsI8=
golang.org/x/sys v0.0.0-20220727055044-e65921a090b8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220730100132-1609e554cd39 h1:aNCnH+Fiqs7ZDTFH6oEFjIfbX2HvgQXJ6uQuUbTobjk=
golang.org/x/sys v0.0.0-20220730100132-1609e554cd39/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220731174439-a90be440212d h1:Sv5ogFZatcgIMMtBSTTAgMYsicp25MXBubjXNDKwm80=
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220803195053-6e608f9ce704 h1:Y7NOhdqIOU8kYI7BxsgL38d0ot0raxvcW+EMQU2QrT4=
golang.org/x/sys v0.0.0-20220803195053-6e608f9ce704/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -308,8 +284,8 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY=
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -3,7 +3,7 @@
package logawl
import (
"fmt"
"errors"
"io"
"sync"
"sync/atomic"
@ -14,10 +14,10 @@ type (
Logger struct {
Mu sync.Mutex
Level Level
isDiscard int32
Prefix string
Out io.Writer
buf []byte
isDiscard int32
}
)
@ -48,7 +48,7 @@ func (l *Logger) UnMarshalLevel(lv Level) (string, error) {
case 3:
return "DEBUG ", nil
}
return "", fmt.Errorf("invalid log level")
return "", errInvalidLevel
}
func (l *Logger) IsLevel(level Level) bool {
@ -74,3 +74,5 @@ const (
// Verbose log level.
DebugLevel
)
var errInvalidLevel = errors.New("invalid log level")

View File

@ -11,41 +11,41 @@ import (
// Calling New instantiates Logger
//
// Level can be changed to one of the other log levels (FatalLevel, ErrorLevel, InfoLevel, DebugLevel)
// Level can be changed to one of the other log levels (ErrorLevel, WarnLevel, InfoLevel, DebugLevel).
func New() *Logger {
return &Logger{
Out: os.Stderr,
Level: InfoLevel, //Default value is InfoLevel
Level: WarnLevel, // Default value is WarnLevel
}
}
// Takes any and prints it out to Logger -> Out (io.Writer (default is std.Err))
// 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
}
//If verbose is not set --debug etc print _nothing_
// 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 (Fatal os.Exit...etc)
switch level { // Goes through log levels and does stuff based on them (currently nothing)
case 0:
err := l.Printer(0, fmt.Sprintln(v...)) //Fatal level
err := l.Printer(0, fmt.Sprintln(v...)) // Error level
if err != nil {
fmt.Fprintln(os.Stderr, "FATAL: Logger failed: ", err)
fmt.Fprintln(os.Stderr, "Logger failed: ", err)
}
case 1:
err := l.Printer(1, fmt.Sprintln(v...)) //Error level
err := l.Printer(1, fmt.Sprintln(v...)) // Warn level
if err != nil {
fmt.Fprintln(os.Stderr, "FATAL: Logger failed: ", err)
fmt.Fprintln(os.Stderr, "Logger failed: ", err)
}
case 2:
err := l.Printer(2, fmt.Sprintln(v...)) //Info level
err := l.Printer(2, fmt.Sprintln(v...)) // Info level
if err != nil {
fmt.Fprintln(os.Stderr, "FATAL: Logger failed: ", err)
fmt.Fprintln(os.Stderr, "Logger failed: ", err)
}
case 3:
err := l.Printer(3, fmt.Sprintln(v...)) //Debug level
err := l.Printer(3, fmt.Sprintln(v...)) // Debug level
if err != nil {
fmt.Fprintln(os.Stderr, "FATAL: Logger failed: ", err)
fmt.Fprintln(os.Stderr, "Logger failed: ", err)
}
default:
break
@ -53,7 +53,7 @@ func (l *Logger) Println(level Level, v ...any) {
}
}
// Formats the log header as such <LogLevel> YYYY/MM/DD HH:MM:SS (local time) <the message to log>
// 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
@ -77,12 +77,12 @@ func (l *Logger) FormatHeader(buf *[]byte, t time.Time, line int, level Level) e
*buf = append(*buf, ':')
*buf = append(*buf, ' ')
} else {
return fmt.Errorf("invalid log level choice")
return errInvalidLevel
}
return nil
}
// Printer prints the formatted message directly to stdErr
// Printer prints the formatted message directly to stdErr.
func (l *Logger) Printer(level Level, s string) error {
now := time.Now()
var line int
@ -99,7 +99,10 @@ func (l *Logger) Printer(level Level, s string) error {
l.buf = append(l.buf, '\n')
}
_, err = l.Out.Write(l.buf)
return err
if err != nil {
return fmt.Errorf("logger printing error %w", err)
}
return nil
}
// Some line formatting stuff from Golang log stdlib file
@ -123,22 +126,22 @@ func formatter(buf *[]byte, i int, wid int) {
*buf = append(*buf, b[bp:]...)
}
// Call print directly with Debug level
// Call print directly with Debug level.
func (l *Logger) Debug(v ...any) {
l.Println(DebugLevel, v...)
}
// Call print directly with Info level
// Call print directly with Info level.
func (l *Logger) Info(v ...any) {
l.Println(InfoLevel, v...)
}
// Call print directly with Warn level
// Call print directly with Warn level.
func (l *Logger) Warn(v ...any) {
l.Println(WarnLevel, v...)
}
// Call print directly with Error level
// Call print directly with Error level.
func (l *Logger) Error(v ...any) {
l.Println(ErrLevel, v...)
}

View File

@ -8,7 +8,6 @@ import (
"time"
"git.froth.zone/sam/awl/logawl"
"gotest.tools/v3/assert"
)
@ -48,11 +47,17 @@ func TestLogger(t *testing.T) {
t.Parallel()
for i := range logawl.AllLevels {
// only test non-exiting log levels
switch i {
case 0:
fn := func() {
logger.Error("Test", "E")
}
var buffer bytes.Buffer
logger.Out = &buffer
fn()
case 1:
fn := func() {
logger.Info("")
logger.Warn("Test")
}
var buffer bytes.Buffer
logger.Out = &buffer
@ -67,6 +72,7 @@ func TestLogger(t *testing.T) {
case 3:
fn := func() {
logger.Debug("Test")
logger.Debug("Test 2")
}
var buffer bytes.Buffer
logger.Out = &buffer
@ -79,6 +85,6 @@ func TestFmt(t *testing.T) {
t.Parallel()
ti := time.Now()
test := []byte("test")
assert.ErrorContains(t, logger.FormatHeader(&test, ti, 0, 9001), "invalid log level") //make sure error is error
// make sure error is error
assert.ErrorContains(t, logger.FormatHeader(&test, ti, 0, 9001), "invalid log level")
}

93
main.go
View File

@ -3,20 +3,14 @@
package main
import (
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"os"
"strconv"
"strings"
"time"
"git.froth.zone/sam/awl/cli"
"git.froth.zone/sam/awl/internal/helpers"
"git.froth.zone/sam/awl/query"
"gopkg.in/yaml.v2"
)
var version = "DEV"
@ -31,6 +25,7 @@ func main() {
opts.Logger.Error(err)
os.Exit(1)
}
var resp helpers.Response
// Retry queries if a query fails
@ -39,7 +34,7 @@ func main() {
if err == nil {
break
} else {
opts.Logger.Warn("Retrying request, error", err)
opts.Logger.Warn("Retrying request, error:", err)
}
}
@ -48,83 +43,17 @@ func main() {
opts.Logger.Error(err)
os.Exit(9)
}
switch {
case opts.JSON:
opts.Logger.Info("Printing as JSON")
json, err := json.MarshalIndent(resp.DNS, "", " ")
var str string
if opts.JSON || opts.XML || opts.YAML {
str, err = query.PrintSpecial(resp.DNS, opts)
if err != nil {
opts.Logger.Error(err)
opts.Logger.Error("Special print:", err)
os.Exit(10)
}
fmt.Println(string(json))
case opts.XML:
opts.Logger.Info("Printing as XML")
xml, err := xml.MarshalIndent(resp.DNS, "", " ")
if err != nil {
opts.Logger.Error(err)
os.Exit(10)
}
fmt.Println(string(xml))
case opts.YAML:
opts.Logger.Info("Printing as YAML")
yaml, err := yaml.Marshal(resp.DNS)
if err != nil {
opts.Logger.Error(err)
os.Exit(10)
}
fmt.Println(string(yaml))
default:
if !opts.Short {
// Print everything
if !opts.Display.Question {
resp.DNS.Question = nil
opts.Logger.Info("Disabled question display")
}
if !opts.Display.Answer {
resp.DNS.Answer = nil
opts.Logger.Info("Disabled answer display")
}
if !opts.Display.Authority {
resp.DNS.Ns = nil
opts.Logger.Info("Disabled authority display")
}
if !opts.Display.Additional {
resp.DNS.Extra = nil
opts.Logger.Info("Disabled additional display")
}
fmt.Println(resp.DNS)
if opts.Display.Statistics {
fmt.Println(";; Query time:", resp.RTT)
// Add extra information to server string
var extra string
switch {
case opts.TCP:
extra = ":" + strconv.Itoa(opts.Port) + " (TCP)"
case opts.TLS:
extra = ":" + strconv.Itoa(opts.Port) + " (TLS)"
case opts.HTTPS, opts.DNSCrypt:
extra = ""
case opts.QUIC:
extra = ":" + strconv.Itoa(opts.Port) + " (QUIC)"
default:
extra = ":" + strconv.Itoa(opts.Port) + " (UDP)"
}
fmt.Println(";; SERVER:", opts.Request.Server+extra)
fmt.Println(";; WHEN:", time.Now().Format(time.RFC1123Z))
fmt.Println(";; MSG SIZE rcvd:", resp.DNS.Len())
}
} else {
// Print just the responses, nothing else
for _, res := range resp.DNS.Answer {
temp := strings.Split(res.String(), "\t")
fmt.Println(temp[len(temp)-1])
}
}
} else {
str = query.ToString(resp, opts)
}
fmt.Println(str)
}

19
main_test.go Normal file
View File

@ -0,0 +1,19 @@
// SPDX-License-Identifier: BSD-3-Clause
package main
import (
"os"
"testing"
"gotest.tools/v3/assert"
)
// nolint: paralleltest
func TestMain(t *testing.T) {
os.Args = []string{"awl", "+yaml", "@1.1.1.1"}
main()
os.Args = []string{"awl", "+short", "@1.1.1.1"}
main()
assert.Assert(t, 1 == 2-1)
}

10
mkfile
View File

@ -3,20 +3,18 @@
GO = go
PROG = awl
LDFLAGS = '-s -w'
GOFLAGS = -ldflags=$LDFLAGS
CGO_ENABLED = 0
$PROG:
$GO build $GOFLAGS -o $PROG '-buildvcs=false' .
$GO build -ldflags="-s -w -X=main.version=PLAN9" -o $PROG .
install: $PROG
$GO install $GOFLAGS .
install:
$GO install -ldflags="-s -w -X=main.version=PLAN9" .
cp doc/$PROG.1 /sys/man/1/$PROG
test:
$GO test -v -cover -coverprofile=coverage/coverage.out ./...
$GO test -cover -coverprofile=coverage/coverage.out ./...
fmt:
gofmt -w -s .

View File

@ -3,11 +3,11 @@
package query
import (
"fmt"
"time"
"git.froth.zone/sam/awl/cli"
"git.froth.zone/sam/awl/internal/helpers"
"github.com/ameshkov/dnscrypt/v2"
"github.com/miekg/dns"
)
@ -16,8 +16,8 @@ type DNSCryptResolver struct {
opts cli.Options
}
// LookUp performs a DNS query.
func (r *DNSCryptResolver) LookUp(msg *dns.Msg) (helpers.Response, error) {
client := dnscrypt.Client{
Timeout: r.opts.Request.Timeout,
UDPSize: 1232,
@ -39,7 +39,7 @@ func (r *DNSCryptResolver) LookUp(msg *dns.Msg) (helpers.Response, error) {
resolverInf, err := client.Dial(r.opts.Request.Server)
if err != nil {
return helpers.Response{}, err
return helpers.Response{}, fmt.Errorf("dnscrypt: dial error: %w", err)
}
now := time.Now()
@ -47,7 +47,7 @@ func (r *DNSCryptResolver) LookUp(msg *dns.Msg) (helpers.Response, error) {
rtt := time.Since(now)
if err != nil {
return helpers.Response{}, err
return helpers.Response{}, fmt.Errorf("dnscrypt: exchange error: %w", err)
}
r.opts.Logger.Info("Request successful")

View File

@ -9,12 +9,12 @@ import (
"git.froth.zone/sam/awl/internal/helpers"
"git.froth.zone/sam/awl/query"
"git.froth.zone/sam/awl/util"
"github.com/miekg/dns"
"gotest.tools/v3/assert"
)
func TestDNSCrypt(t *testing.T) {
t.Parallel()
tests := []struct {
opt cli.Options
}{
@ -42,11 +42,30 @@ func TestDNSCrypt(t *testing.T) {
},
},
},
{
cli.Options{
Logger: util.InitLogger(0),
DNSCrypt: true,
TCP: true,
IPv4: true,
Request: helpers.Request{
Server: "QMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
Type: dns.TypeAAAA,
Name: "example.com.",
},
},
},
}
for _, test := range tests {
res, err := query.CreateQuery(test.opt)
assert.NilError(t, err)
assert.Assert(t, res != helpers.Response{})
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{})
} else {
assert.ErrorContains(t, err, "unsupported stamp")
}
})
}
}

View File

@ -18,6 +18,7 @@ type HTTPSResolver struct {
opts cli.Options
}
// LookUp performs a DNS query.
func (r *HTTPSResolver) LookUp(msg *dns.Msg) (helpers.Response, error) {
var resp helpers.Response
httpR := &http.Client{
@ -25,13 +26,13 @@ func (r *HTTPSResolver) LookUp(msg *dns.Msg) (helpers.Response, error) {
}
buf, err := msg.Pack()
if err != nil {
return helpers.Response{}, err
return helpers.Response{}, fmt.Errorf("doh: packing error error: %w", err)
}
r.opts.Logger.Debug("making DoH request")
// query := server + "?dns=" + base64.RawURLEncoding.EncodeToString(buf)
//
req, err := http.NewRequest("POST", r.opts.Request.Server, bytes.NewBuffer(buf))
if err != nil {
return helpers.Response{}, fmt.Errorf("DoH: %w", err)
return helpers.Response{}, fmt.Errorf("doh request creation error: %w", err)
}
req.Header.Set("Content-Type", "application/dns-message")
req.Header.Set("Accept", "application/dns-message")
@ -41,24 +42,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 helpers.Response{}, fmt.Errorf("doh HTTP request error: %w", err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return helpers.Response{}, fmt.Errorf("DoH server responded with HTTP %d", res.StatusCode)
return helpers.Response{}, &errHTTPStatus{res.StatusCode}
}
fullRes, err := io.ReadAll(res.Body)
if err != nil {
return helpers.Response{}, fmt.Errorf("DoH body read error: %w", err)
return helpers.Response{}, fmt.Errorf("doh body read error: %w", err)
}
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 helpers.Response{}, fmt.Errorf("doh dns message unpack error: %w", err)
}
return resp, nil
}
type errHTTPStatus struct {
code int
}
func (e *errHTTPStatus) Error() string {
return fmt.Sprintf("doh server responded with HTTP %d", e.code)
}

View File

@ -11,7 +11,6 @@ import (
"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"
)

View File

@ -4,12 +4,12 @@ package query
import (
"crypto/tls"
"fmt"
"io"
"time"
"git.froth.zone/sam/awl/cli"
"git.froth.zone/sam/awl/internal/helpers"
"github.com/lucas-clemente/quic-go"
"github.com/miekg/dns"
)
@ -18,6 +18,7 @@ type QUICResolver struct {
opts cli.Options
}
// LookUp performs a DNS query.
func (r *QUICResolver) LookUp(msg *dns.Msg) (helpers.Response, error) {
var resp helpers.Response
tls := &tls.Config{
@ -31,46 +32,46 @@ func (r *QUICResolver) LookUp(msg *dns.Msg) (helpers.Response, error) {
r.opts.Logger.Debug("making DoQ request")
connection, err := quic.DialAddr(r.opts.Request.Server, tls, conf)
if err != nil {
return helpers.Response{}, err
return helpers.Response{}, fmt.Errorf("doq: dial error: %w", err)
}
// Compress request to over-the-wire
buf, err := msg.Pack()
if err != nil {
return helpers.Response{}, err
return helpers.Response{}, fmt.Errorf("doq: pack error: %w", err)
}
t := time.Now()
stream, err := connection.OpenStream()
if err != nil {
return helpers.Response{}, err
return helpers.Response{}, fmt.Errorf("doq: quic stream creation error: %w", err)
}
_, err = stream.Write(buf)
if err != nil {
return helpers.Response{}, err
return helpers.Response{}, fmt.Errorf("doq: quic stream write error: %w", err)
}
fullRes, err := io.ReadAll(stream)
if err != nil {
return helpers.Response{}, err
return helpers.Response{}, fmt.Errorf("doq: quic stream read error: %w", err)
}
resp.RTT = time.Since(t)
// Close with error: no error
err = connection.CloseWithError(0, "")
if err != nil {
return helpers.Response{}, err
return helpers.Response{}, fmt.Errorf("doq: quic connection close error: %w", err)
}
err = stream.Close()
if err != nil {
return helpers.Response{}, err
return helpers.Response{}, fmt.Errorf("doq: quic stream close error: %w", err)
}
resp.DNS = &dns.Msg{}
r.opts.Logger.Debug("unpacking DoQ response")
err = resp.DNS.Unpack(fullRes)
if err != nil {
return helpers.Response{}, err
return helpers.Response{}, fmt.Errorf("doq: upack error: %w", err)
}
return resp, nil
}

View File

@ -14,7 +14,6 @@ import (
"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"
)

View File

@ -8,7 +8,6 @@ import (
"git.froth.zone/sam/awl/cli"
"git.froth.zone/sam/awl/internal/helpers"
"github.com/miekg/dns"
)
@ -16,6 +15,7 @@ type StandardResolver struct {
opts cli.Options
}
// LookUp performs a DNS query
func (r *StandardResolver) LookUp(msg *dns.Msg) (helpers.Response, error) {
var (
resp helpers.Response
@ -45,7 +45,7 @@ func (r *StandardResolver) LookUp(msg *dns.Msg) (helpers.Response, error) {
resp.DNS, resp.RTT, err = dnsClient.Exchange(msg, r.opts.Request.Server)
if err != nil {
return helpers.Response{}, err
return helpers.Response{}, fmt.Errorf("standard: DNS exchange error: %w", err)
}
r.opts.Logger.Info("Request successful")
@ -55,11 +55,14 @@ func (r *StandardResolver) LookUp(msg *dns.Msg) (helpers.Response, error) {
switch {
case r.opts.IPv4:
dnsClient.Net += "4"
case r.opts.IPv4:
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 resp, err
return resp, nil
}

View File

@ -4,6 +4,7 @@ package query_test
import (
"testing"
"time"
"git.froth.zone/sam/awl/cli"
"git.froth.zone/sam/awl/internal/helpers"
@ -14,25 +15,28 @@ import (
)
func TestResolve(t *testing.T) {
t.Parallel()
opts := cli.Options{
Logger: util.InitLogger(0),
Port: 53,
Request: helpers.Request{
Server: "8.8.4.4",
Type: dns.TypeA,
Name: "example.com.",
Server: "8.8.4.1",
Type: dns.TypeA,
Name: "example.com.",
Timeout: time.Second / 2,
Retries: 0,
},
}
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{})
_, err = resolver.LookUp(msg)
assert.ErrorContains(t, err, "timeout")
}
func TestTruncate(t *testing.T) {
t.Parallel()
opts := cli.Options{
Logger: util.InitLogger(0),
IPv4: true,
@ -53,6 +57,7 @@ func TestTruncate(t *testing.T) {
}
func TestResolveAgain(t *testing.T) {
t.Parallel()
tests := []struct {
opt cli.Options
}{
@ -79,11 +84,26 @@ func TestResolveAgain(t *testing.T) {
},
},
},
{
cli.Options{
Logger: util.InitLogger(0),
TLS: true,
Port: 853,
Request: helpers.Request{
Server: "dns.google",
Type: dns.TypeAAAA,
Name: "example.com.",
},
},
},
}
for _, test := range tests {
res, err := query.CreateQuery(test.opt)
assert.NilError(t, err)
assert.Assert(t, res != helpers.Response{})
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{})
})
}
}

177
query/print.go Normal file
View File

@ -0,0 +1,177 @@
// SPDX-License-Identifier: BSD-3-Clause
package query
import (
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"strconv"
"strings"
"time"
"git.froth.zone/sam/awl/cli"
"github.com/miekg/dns"
"golang.org/x/net/idna"
"gopkg.in/yaml.v3"
)
func PrintSpecial(msg *dns.Msg, opts cli.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
case opts.XML:
opts.Logger.Info("Printing as XML")
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
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) {
var err error
ret := Message{
Header: msg.MsgHdr,
}
for _, question := range msg.Question {
var name string
if opts.Display.UcodeTranslate {
name, err = idna.ToUnicode(question.Name)
if err != nil {
return nil, fmt.Errorf("punycode: error translating to unicode: %w", err)
}
} else {
name = question.Name
}
ret.Question = append(ret.Question, Question{
Name: name,
Type: dns.TypeToString[question.Qtype],
Class: dns.ClassToString[question.Qclass],
})
}
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()
} else {
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)
}
} else {
name = answer.Header().Name
}
ret.Answer = append(ret.Answer, Answer{
RRHeader: RRHeader{
Name: name,
Type: dns.TypeToString[answer.Header().Rrtype],
Class: dns.ClassToString[answer.Header().Class],
Rdlength: answer.Header().Rdlength,
TTL: ttl,
},
Value: temp[len(temp)-1],
})
}
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()
} else {
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)
}
} else {
name = ns.Header().Name
}
ret.Ns = append(ret.Ns, Answer{
RRHeader: RRHeader{
Name: name,
Type: dns.TypeToString[ns.Header().Rrtype],
Class: dns.ClassToString[ns.Header().Class],
Rdlength: ns.Header().Rdlength,
TTL: ttl,
},
Value: temp[len(temp)-1],
})
}
for _, additional := range msg.Extra {
if additional.Header().Rrtype == dns.StringToType["OPT"] {
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()
} else {
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)
}
} else {
name = additional.Header().Name
}
ret.Extra = append(ret.Extra, Answer{
RRHeader: RRHeader{
Name: name,
Type: dns.TypeToString[additional.Header().Rrtype],
Class: dns.ClassToString[additional.Header().Class],
Rdlength: additional.Header().Rdlength,
TTL: ttl,
},
Value: temp[len(temp)-1],
})
}
}
return &ret, nil
}
var errInvalidFormat = errors.New("this should never happen")

343
query/print_test.go Normal file
View File

@ -0,0 +1,343 @@
// SPDX-License-Identifier: BSD-3-Clause
package query_test
import (
"testing"
"git.froth.zone/sam/awl/cli"
"git.froth.zone/sam/awl/internal/helpers"
"git.froth.zone/sam/awl/query"
"git.froth.zone/sam/awl/util"
"github.com/miekg/dns"
"gotest.tools/v3/assert"
)
func TestBadFormat(t *testing.T) {
t.Parallel()
_, err := query.PrintSpecial(new(dns.Msg), cli.Options{})
assert.ErrorContains(t, err, "never happen")
}
func TestPrinting(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")
}

View File

@ -4,33 +4,114 @@ package query
import (
"fmt"
"strconv"
"git.froth.zone/sam/awl/cli"
"git.froth.zone/sam/awl/internal/helpers"
"github.com/dchest/uniuri"
"github.com/miekg/dns"
)
func CreateQuery(opts cli.Options) (helpers.Response, error) {
var res helpers.Response
res.DNS = new(dns.Msg)
res.DNS.SetQuestion(opts.Request.Name, opts.Request.Type)
res.DNS.Question[0].Qclass = opts.Request.Class
req := new(dns.Msg)
req.SetQuestion(opts.Request.Name, opts.Request.Type)
req.Question[0].Qclass = opts.Request.Class
res.DNS.MsgHdr.Response = opts.QR
res.DNS.MsgHdr.Authoritative = opts.AA
res.DNS.MsgHdr.Truncated = opts.TC
res.DNS.MsgHdr.RecursionDesired = opts.RD
res.DNS.MsgHdr.RecursionAvailable = opts.RA
res.DNS.MsgHdr.Zero = opts.Z
res.DNS.MsgHdr.AuthenticatedData = opts.AD
res.DNS.MsgHdr.CheckingDisabled = opts.CD
// Set standard flags
req.MsgHdr.Response = opts.QR
req.MsgHdr.Authoritative = opts.AA
req.MsgHdr.Truncated = opts.TC
req.MsgHdr.RecursionDesired = opts.RD
req.MsgHdr.RecursionAvailable = opts.RA
req.MsgHdr.Zero = opts.Z
req.MsgHdr.AuthenticatedData = opts.AD
req.MsgHdr.CheckingDisabled = opts.CD
if opts.DNSSEC {
res.DNS.SetEdns0(1232, true)
// EDNS time :)
if opts.EDNS.EnableEDNS {
o := new(dns.OPT)
o.Hdr.Name = "."
o.Hdr.Rrtype = dns.TypeOPT
o.SetVersion(opts.EDNS.Version)
if opts.EDNS.Cookie {
e := new(dns.EDNS0_COOKIE)
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")
}
if opts.EDNS.Subnet.Address != nil {
o.Option = append(o.Option, &opts.EDNS.Subnet)
}
req.Extra = append(req.Extra, o)
} else if opts.EDNS.DNSSEC {
req.SetEdns0(1232, true)
opts.Logger.Warn("DNSSEC implies EDNS, EDNS enabled")
opts.Logger.Info("DNSSEC enabled, UDP buffer set to 1232")
}
opts.Logger.Debug(req)
opts.Logger.Debug(fmt.Sprintf("%+v", res))
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
}
} else {
temp := opts.Display.Statistics
opts.Display.Statistics = false
str = ToString(helpers.Response{
DNS: req,
RTT: 0,
}, opts)
opts.Display.Statistics = temp
str += "\n;; QUERY SIZE: " + strconv.Itoa(req.Len())
}
fmt.Println(str)
opts.ShowQuery = false
}
}
resolver, err := LoadResolver(opts)
if err != nil {
@ -38,5 +119,6 @@ func CreateQuery(opts cli.Options) (helpers.Response, error) {
}
opts.Logger.Info("Query successfully loaded")
return resolver.LookUp(res.DNS)
//nolint:wrapcheck // Error wrapping not needed here
return resolver.LookUp(req)
}

View File

@ -9,26 +9,92 @@ import (
"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 TestCreateQ(t *testing.T) {
opts := cli.Options{
Logger: util.InitLogger(0),
Port: 53,
QR: false,
Z: true,
RD: false,
DNSSEC: true,
Request: helpers.Request{
Server: "8.8.4.4",
Type: dns.TypeA,
Name: "example.com.",
t.Parallel()
in := []cli.Options{
{
Logger: util.InitLogger(0),
Port: 53,
QR: false,
Z: true,
RD: false,
ShowQuery: true,
YAML: true,
Request: helpers.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,
},
EDNS: cli.EDNS{
EnableEDNS: true,
DNSSEC: true,
Cookie: true,
Expire: true,
KeepOpen: true,
Nsid: true,
},
},
{
Logger: util.InitLogger(0),
Port: 53,
QR: false,
Z: true,
RD: false,
ShowQuery: true,
XML: true,
Request: helpers.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: true,
},
EDNS: cli.EDNS{
EnableEDNS: false,
DNSSEC: false,
Cookie: false,
Expire: false,
KeepOpen: false,
Nsid: false,
},
},
}
res, err := query.CreateQuery(opts)
assert.NilError(t, err)
assert.Assert(t, res != helpers.Response{})
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{})
str, err := query.PrintSpecial(res.DNS, opt)
assert.NilError(t, err)
assert.Assert(t, str != "")
str = query.ToString(res, opt)
assert.Assert(t, str != "")
})
}
}

View File

@ -12,10 +12,12 @@ import (
"github.com/miekg/dns"
)
// Main resolver interface
type Resolver interface {
LookUp(*dns.Msg) (helpers.Response, error)
}
// LoadResolver loads the respective resolver for performing a DNS query
func LoadResolver(opts cli.Options) (Resolver, error) {
switch {
case opts.HTTPS:

153
query/struct.go Normal file
View File

@ -0,0 +1,153 @@
// SPDX-License-Identifier: BSD-3-Clause
package query
import (
"strconv"
"strings"
"time"
"git.froth.zone/sam/awl/cli"
"git.froth.zone/sam/awl/internal/helpers"
"github.com/miekg/dns"
)
// Overall DNS response message
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"`
}
// 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
type RRHeader 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"`
TTL string `json:"ttl,omitempty" xml:"ttl,omitempty" yaml:",omitempty"`
Rdlength uint16 `json:"-" xml:"-" yaml:"-"`
}
// DNS Response
type Answer struct {
RRHeader `json:"header,omitempty" xml:"header,omitempty" yaml:"header,omitempty"`
Value string `json:"response,omitempty" xml:"response,omitempty" yaml:"response,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 {
if res.DNS == nil {
return "<nil> MsgHdr"
}
var s string
var opt *dns.OPT
if !opts.Short {
if opts.Display.Comments {
s += res.DNS.MsgHdr.String() + " "
s += "QUERY: " + strconv.Itoa(len(res.DNS.Question)) + ", "
s += "ANSWER: " + strconv.Itoa(len(res.DNS.Answer)) + ", "
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"
}
}
}
}
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"
}
}
}
}
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"
}
}
}
}
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)"
case opts.TLS:
extra = ":" + strconv.Itoa(opts.Port) + " (TLS)"
case opts.HTTPS, opts.DNSCrypt:
extra = ""
case opts.QUIC:
extra = ":" + strconv.Itoa(opts.Port) + " (QUIC)"
default:
extra = ":" + strconv.Itoa(opts.Port) + " (UDP)"
}
s += "\n;; SERVER: " + opts.Request.Server + extra
s += "\n;; WHEN: " + time.Now().Format(time.RFC1123Z)
s += "\n;; MSG SIZE rcvd: " + strconv.Itoa(res.DNS.Len()) + "\n"
}
} else {
// Print just the responses, nothing else
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"
}
}
}
return s
}

14
query/struct_test.go Normal file
View File

@ -0,0 +1,14 @@
// 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)
}

View File

@ -1,13 +1,13 @@
# SPDX-License-Identifier: BSD-3-Clause
# Template for the BSD/GNU makefiles
HASH ?= $(shell git describe --always --dirty || echo "UNKNOWN")
HASH ?= `git describe --always --dirty || echo "UNKNOWN"`
VER ?= "git-$(HASH)"
CGO_ENABLED ?= 0
GO ?= go
GOFLAGS ?= -ldflags "-s -w -X 'main.version=$(VER)'" -buildvcs=false
COVER ?= $(GO) tool cover
GOFLAGS ?= -ldflags "-s -w -X=main.version=$(VER)" -trimpath
PREFIX ?= /usr/local
BIN ?= bin
@ -20,19 +20,21 @@ PROG ?= awl
# hehe
all: $(PROG) doc/$(PROG).1
doc/$(PROG).1: doc/wiki/$(PROG).1.md
@cp doc/awl.1 doc/awl.bak
$(SCDOC) <doc/wiki/$(PROG).1.md >doc/$(PROG).1 2>/dev/null && rm doc/awl.bak || mv doc/awl.bak doc/awl.1
$(SCDOC) <doc/wiki/$(PROG).1.md >doc/$(PROG).1 && rm doc/awl.bak || mv doc/awl.bak doc/awl.1
## test: run go test
test:
$(GO) test -cover -coverprofile=coverage/coverage.out ./...
coverage/coverage.out: test
$(COVER) -func=coverage/coverage.out
$(COVER) -html=coverage/coverage.out -o coverage/cover.html
## cover: generates test coverage, output as HTML
cover: test
$(GO) tool cover -func=coverage/coverage.out
$(GO) tool cover -html=coverage/coverage.out -o coverage/cover.html
cover: coverage/coverage.out
fmt:
gofmt -w -s .
@ -42,7 +44,7 @@ vet:
## lint: lint awl, using fmt, vet and golangci-lint
lint: fmt vet
-golangci-lint run
-golangci-lint run --fix
## clean: clean the build files
clean:
@ -51,6 +53,6 @@ clean:
## help: Prints this help message
help:
@echo "Usage: "
@sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /'
@sed -n 's/^##//p' $(MAKEFILE_LIST) | column -t -s ':' | sed -e 's/^/ /'
.PHONY: clean lint test fmt vet help

View File

@ -5,10 +5,10 @@ package util
import "git.froth.zone/sam/awl/logawl"
// Initialize the logawl instance.
func InitLogger(verbosity int) (Logger *logawl.Logger) {
Logger = logawl.New()
func InitLogger(verbosity int) (logger *logawl.Logger) {
logger = logawl.New()
Logger.SetLevel(logawl.Level(verbosity))
logger.SetLevel(logawl.Level(verbosity))
return
return logger
}

View File

@ -11,6 +11,7 @@ import (
)
func TestInitLogger(t *testing.T) {
t.Parallel()
logger := util.InitLogger(0)
assert.Equal(t, logger.Level, logawl.Level(0))
}

View File

@ -3,18 +3,29 @@
package util
import (
"errors"
"fmt"
"strings"
"github.com/miekg/dns"
)
type errReverseDNS struct {
addr string
}
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.
func ReverseDNS(address string, querInt uint16) (string, error) {
query := dns.TypeToString[querInt]
if query == "PTR" {
return dns.ReverseAddr(address)
str, err := dns.ReverseAddr(address)
if err != nil {
return "", fmt.Errorf("could not reverse: %w", err)
}
return str, nil
} else if query == "NAPTR" {
// get rid of characters not needed
replacer := strings.NewReplacer("+", "", " ", "", "-", "")
@ -30,7 +41,7 @@ func ReverseDNS(address string, querInt uint16) (string, error) {
return arpa.String(), nil
}
return "", errors.New("ReverseDNS: -x flag given but no IP found")
return "", &errReverseDNS{address}
}
// Reverse a string, return the string in reverse.

View File

@ -6,7 +6,6 @@ import (
"testing"
"git.froth.zone/sam/awl/util"
"github.com/miekg/dns"
"gotest.tools/v3/assert"
)
@ -56,5 +55,11 @@ func TestNAPTR(t *testing.T) {
func TestInvalid(t *testing.T) {
t.Parallel()
_, err := util.ReverseDNS("AAAAA", 1)
assert.ErrorContains(t, err, "no IP found")
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")
}