diff --git a/.drone.jsonnet b/.drone.jsonnet index 6ac4e64..e692f51 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -1,102 +1,144 @@ // SPDX-License-Identifier: BSD-3-Clause local testing(version, arch) = { - kind: "pipeline", - type: "docker", - name: version + "-" + arch , + kind: 'pipeline', + type: 'docker', + name: version + '-' + arch, platform: { - arch: arch + arch: arch, }, steps: [ { - name: "compile", - image: "golang:" + version, + name: 'lint', + image: 'rancher/drone-golangci-lint:latest', + }, + { + name: 'cache', + image: 'golang:' + version, commands: [ - "make awl" + 'go mod tidy' + ], + depends_on: [ + 'lint', + ], + volumes: [ + { + name: 'cache', + path: '/go', + }, ], }, { - name: "lint", - image: "rancher/drone-golangci-lint:latest", + name: 'test', + image: 'golang:' + version, + commands: [ + 'make test-ci', + ], depends_on: [ - "compile", + 'cache', + ], + volumes: [ + { + name: 'cache', + path: '/go', + }, ], }, { - name: "test", - image: "golang:" + version, + name: 'fuzz', + image: 'golang:' + version, commands: [ - "make test-ci" + 'make fuzz-ci', ], depends_on: [ - "lint", + 'cache', ], - }, - { - name: "fuzz", - image: "golang:" + version, - commands: [ - "make fuzz-ci", - ], - depends_on: [ - "lint", + volumes: [ + { + name: 'cache', + path: '/go', + }, ], }, ], trigger: { event: { exclude: [ - "tag" + 'tag', ], - } + }, }, + volumes: [ + { + name: 'cache', + temp: {}, + }, + ], }; // "Inspired by" https://goreleaser.com/ci/drone/ local release() = { - kind: "pipeline", - type: "docker", - name: "release", + kind: 'pipeline', + type: 'docker', + name: 'release', trigger: { event: [ - "tag" + 'tag', ], }, steps: [ { - name: "fetch", - image: "alpine/git", - commands : [ - "git fetch --tags", - ] - }, - { - name: "test", - image: "golang", + name: 'fetch', + image: 'alpine/git', commands: [ - "make test" - ] + 'git fetch --tags', + ], }, { - name: "release", - image: "goreleaser/goreleaser", + name: 'test', + image: 'golang', + commands: [ + 'make test-ci', + ], + volumes: [ + { + name: 'cache', + path: '/go', + }, + ], + }, + { + name: 'release', + image: 'goreleaser/goreleaser', environment: { - "GITEA_TOKEN": { - from_secret: "GITEA_TOKEN" - } + GITEA_TOKEN: { + from_secret: 'GITEA_TOKEN', + }, }, commands: [ - "goreleaser release" + 'goreleaser release', ], - } - ] + volumes: [ + { + name: 'cache', + path: '/go', + }, + ], + }, + ], + volumes: [ + { + name: 'cache', + temp: {}, + }, + ], }; [ - testing("1.19", "amd64"), - testing("1.19", "arm64"), - testing("1.18", "amd64"), - testing("1.18", "arm64"), + testing('1.19', 'amd64'), + testing('1.19', 'arm64'), + testing('1.18', 'amd64'), + testing('1.18', 'arm64'), - release() -] \ No newline at end of file + release(), +] diff --git a/.golangci.yaml b/.golangci.yaml index 9eca14e..badf518 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -32,6 +32,7 @@ linters: - predeclared - revive - staticcheck + - tagliatelle - whitespace - wrapcheck - wsl @@ -70,6 +71,16 @@ linters-settings: - name: unexported-return - name: var-declaration - name: var-naming + linters-settings: + tagliatelle: + case: + use-field-name: false + rules: + # Any struct tag type can be used. + # Support string case: `camel`, `pascal`, `kebab`, `snake`, `goCamel`, `goPascal`, `goKebab`, `goSnake`, `upper`, `lower` + json: goCamel + yaml: goCamel + xml: goCamel issues: exclude-use-default: false diff --git a/.goreleaser.yaml b/.goreleaser.yaml index ff26ef9..da446bc 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -13,9 +13,22 @@ builds: - linux - windows - darwin + - freebsd goarch: - amd64 + - arm - arm64 + ignore: + # Windows on ARM, maybe someday + - goos: windows + goarch: arm64 + - goos: windows + goarch: arm + + - goos: darwin + goarch: arm + - goos: freebsd + goarch: arm universal_binaries: - replace: true diff --git a/GNUmakefile b/GNUmakefile index 5e7fab3..66aacd1 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -9,6 +9,13 @@ else EXE := $(PROG) endif +doc/$(PROG).1: doc/$(PROG).1.scd + $(SCDOC) <$< >$@ + +doc/wiki/$(PROG).1.md: doc/$(PROG).1 + pandoc --from man --to gfm -o $@ $< + + ## install: installs awl .PHONY: install ifeq ($(OS),Windows_NT) diff --git a/Makefile b/Makefile index d8d8c68..b5483dd 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,12 @@ include template.mk EXE := $(PROG) +doc/$(PROG).1: doc/$(PROG).1.scd + $(SCDOC) $@ + +doc/wiki/$(PROG).1.md: doc/$(PROG).1 + pandoc --from man --to gfm -o $@ doc/$(PROG).1 + ## install: installs awl .PHONY: install install: all diff --git a/cli/cli.go b/cli/cli.go index 4a61b7d..0833b6c 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -5,7 +5,6 @@ package cli import ( "errors" "fmt" - "os" "runtime" "strconv" "strings" @@ -18,8 +17,8 @@ import ( // ParseCLI parses arguments given from the CLI and passes them into an `Options` // struct. -func ParseCLI(version string) (util.Options, error) { - flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) +func ParseCLI(args []string, version string) (util.Options, error) { + flag.CommandLine = flag.NewFlagSet(args[0], flag.ContinueOnError) flag.Usage = func() { fmt.Println(`awl - drill, writ small @@ -63,7 +62,8 @@ func ParseCLI(version string) (util.Options, error) { subnet = flag.String("subnet", "", "set EDNS client 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)") + badCookie = flag.Bool("no-bad-cookie", false, "ignore BADCOOKIE EDNS responses (default: retry with correct cookie") + 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") @@ -104,7 +104,7 @@ func ParseCLI(version string) (util.Options, error) { flag.CommandLine.SortFlags = false // Parse the flags - if err := flag.CommandLine.Parse(os.Args[1:]); err != nil { + if err := flag.CommandLine.Parse(args[1:]); err != nil { return util.Options{Logger: util.InitLogger(*verbosity)}, fmt.Errorf("flag: %w", err) } @@ -127,6 +127,7 @@ func ParseCLI(version string) (util.Options, error) { HTTPS: *https, QUIC: *quic, Truncate: *truncate, + BadCookie: *badCookie, Reverse: *reverse, JSON: *json, XML: *xml, diff --git a/cli/cli_test.go b/cli/cli_test.go index 59353c9..bc6802b 100644 --- a/cli/cli_test.go +++ b/cli/cli_test.go @@ -12,23 +12,19 @@ import ( ) func TestEmpty(t *testing.T) { - args := os.Args - os.Args = []string{"awl", "-4"} + args := []string{"awl", "-4"} - opts, err := cli.ParseCLI("TEST") + opts, err := cli.ParseCLI(args, "TEST") assert.NilError(t, err) assert.Equal(t, opts.Request.Port, 53) assert.Assert(t, opts.IPv4) - - os.Args = args } func TestTLSPort(t *testing.T) { - args := os.Args - os.Args = []string{"awl", "-T"} + args := []string{"awl", "-T"} - opts, err := cli.ParseCLI("TEST") + opts, err := cli.ParseCLI(args, "TEST") assert.NilError(t, err) assert.Equal(t, opts.Request.Port, 853) @@ -37,131 +33,91 @@ func TestTLSPort(t *testing.T) { } func TestSubnet(t *testing.T) { - args := os.Args - os.Args = []string{"awl", "--subnet", "127.0.0.1/32"} + args := []string{"awl", "--subnet", "127.0.0.1/32"} - opts, err := cli.ParseCLI("TEST") + opts, err := cli.ParseCLI(args, "TEST") assert.NilError(t, err) assert.Equal(t, opts.EDNS.Subnet.Family, uint16(1)) - os.Args = args + args = []string{"awl", "--subnet", "0"} - os.Args = []string{"awl", "--subnet", "0"} - - opts, err = cli.ParseCLI("TEST") + opts, err = cli.ParseCLI(args, "TEST") assert.NilError(t, err) assert.Equal(t, opts.EDNS.Subnet.Family, uint16(1)) - os.Args = args + args = []string{"awl", "--subnet", "::/0"} - os.Args = []string{"awl", "--subnet", "::/0"} - - opts, err = cli.ParseCLI("TEST") + opts, err = cli.ParseCLI(args, "TEST") assert.NilError(t, err) assert.Equal(t, opts.EDNS.Subnet.Family, uint16(2)) - os.Args = args + args = []string{"awl", "--subnet", "/"} - os.Args = []string{"awl", "--subnet", "/"} - - opts, err = cli.ParseCLI("TEST") + opts, err = cli.ParseCLI(args, "TEST") assert.ErrorContains(t, err, "EDNS subnet") - - os.Args = args } -func TestMBZ(t *testing.T) { //nolint: paralleltest // Race conditions - args := os.Args - os.Args = []string{"awl", "--zflag", "G"} +func TestMBZ(t *testing.T) { + args := []string{"awl", "--zflag", "G"} - _, err := cli.ParseCLI("TEST") + _, err := cli.ParseCLI(args, "TEST") assert.ErrorContains(t, err, "EDNS MBZ") - - os.Args = args } -func TestInvalidFlag(t *testing.T) { //nolint: paralleltest // Race conditions - args := os.Args - stdout := os.Stdout - stderr := os.Stderr +func TestInvalidFlag(t *testing.T) { + args := []string{"awl", "--treebug"} - os.Stdout = os.NewFile(0, os.DevNull) - os.Stderr = os.NewFile(0, os.DevNull) - - os.Args = []string{"awl", "--treebug"} - - _, err := cli.ParseCLI("TEST") + _, err := cli.ParseCLI(args, "TEST") assert.ErrorContains(t, err, "unknown flag") - - os.Args = args - os.Stdout = stdout - os.Stderr = stderr } -func TestInvalidDig(t *testing.T) { //nolint: paralleltest // Race conditions - args := os.Args - os.Args = []string{"awl", "+a"} +func TestInvalidDig(t *testing.T) { + args := []string{"awl", "+a"} - _, err := cli.ParseCLI("TEST") + _, err := cli.ParseCLI(args, "TEST") assert.ErrorContains(t, err, "digflags: invalid argument") - - os.Args = args } -func TestVersion(t *testing.T) { //nolint: paralleltest // Race conditions - args := os.Args - stdout := os.Stdout - stderr := os.Stderr +func TestVersion(t *testing.T) { + args := []string{"awl", "--version"} - os.Args = []string{"awl", "--version"} - - _, err := cli.ParseCLI("test") + _, err := cli.ParseCLI(args, "test") assert.ErrorType(t, err, cli.ErrNotError) - - os.Args = args - os.Stdout = stdout - os.Stderr = stderr } -func TestTimeout(t *testing.T) { //nolint: paralleltest // Race conditions +func TestTimeout(t *testing.T) { args := [][]string{ {"awl", "+timeout=0"}, {"awl", "--timeout", "0"}, } for _, test := range args { - args := os.Args - os.Args = test + test := test - opt, err := cli.ParseCLI("TEST") + opt, err := cli.ParseCLI(test, "TEST") assert.NilError(t, err) assert.Equal(t, opt.Request.Timeout, time.Second/2) - - os.Args = args } } -func TestRetries(t *testing.T) { //nolint: paralleltest // Race conditions +func TestRetries(t *testing.T) { args := [][]string{ {"awl", "+retry=-2"}, {"awl", "+tries=-2"}, {"awl", "--retries", "-2"}, } for _, test := range args { - args := os.Args - os.Args = test + test := test - opt, err := cli.ParseCLI("TEST") + opt, err := cli.ParseCLI(test, "TEST") assert.NilError(t, err) assert.Equal(t, opt.Request.Retries, 0) - - os.Args = args } } @@ -175,10 +131,8 @@ func FuzzFlags(f *testing.F) { f.Fuzz(func(t *testing.T, orig string) { // Get rid of outputs - args := os.Args - os.Args = []string{"awl", orig} + args := []string{"awl", orig} //nolint:errcheck,gosec // Only make sure the program does not crash - cli.ParseCLI("TEST") - os.Args = args + cli.ParseCLI(args, "TEST") }) } diff --git a/cli/dig.go b/cli/dig.go index 35ba5fe..5a610c9 100644 --- a/cli/dig.go +++ b/cli/dig.go @@ -73,6 +73,8 @@ func ParseDig(arg string, opts *util.Options) error { opts.TCP = isNo case "ignore": opts.Truncate = isNo + case "badcookie": + opts.BadCookie = !isNo case "tls": opts.TLS = isNo case "dnscrypt": diff --git a/cli/dig_test.go b/cli/dig_test.go index 7073dbf..2b5b94f 100644 --- a/cli/dig_test.go +++ b/cli/dig_test.go @@ -40,6 +40,7 @@ func FuzzDig(f *testing.F) { "tries=2", "tries=b", "tries", "tcp", "vc", "notcp", "novc", "ignore", "noignore", + "badcookie", "nobadcookie", "tls", "notls", "dnscrypt", "nodnscrypt", "https", "nohttps", diff --git a/cli/misc.go b/cli/misc.go index 3011cd6..3beb09d 100644 --- a/cli/misc.go +++ b/cli/misc.go @@ -29,7 +29,7 @@ func ParseMiscArgs(args []string, opts *util.Options) error { switch { case strings.HasPrefix(arg, "tls://"): opts.TLS = true - opts.Request.Server = strings.TrimPrefix(opts.Request.Server, "tls://") + opts.Request.Server = strings.TrimPrefix(arg, "tls://") opts.Logger.Info("DNS-over-TLS implicitly set") case strings.HasPrefix(arg, "https://"): opts.HTTPS = true @@ -37,12 +37,18 @@ func ParseMiscArgs(args []string, opts *util.Options) error { opts.Logger.Info("DNS-over-HTTPS implicitly set") case strings.HasPrefix(arg, "quic://"): opts.QUIC = true - opts.Request.Server = strings.TrimPrefix(opts.Request.Server, "quic://") + opts.Request.Server = strings.TrimPrefix(arg, "quic://") opts.Logger.Info("DNS-over-QUIC implicitly set.") case strings.HasPrefix(arg, "sdns://"): opts.DNSCrypt = true opts.Request.Server = arg opts.Logger.Info("DNSCrypt implicitly set") + case strings.HasPrefix(arg, "tcp://"): + opts.TCP = true + opts.Request.Server = strings.TrimPrefix(arg, "udp://") + opts.Logger.Info("TCP implicitly set") + case strings.HasPrefix(arg, "udp://"): + opts.Request.Server = strings.TrimPrefix(arg, "udp://") default: opts.Request.Server = arg } @@ -117,7 +123,7 @@ func ParseMiscArgs(args []string, opts *util.Options) error { if err != nil { // :^) - opts.Logger.Warn("Could not query system for server. Using localhost") + opts.Logger.Warn("Could not query system for server. Using localhost\n", "Error:", err) opts.Request.Server = "127.0.0.1" } else { // Make sure that if IPv4 or IPv6 is asked for it actually uses it diff --git a/cli/misc_test.go b/cli/misc_test.go index 3176cd3..7218418 100644 --- a/cli/misc_test.go +++ b/cli/misc_test.go @@ -127,6 +127,8 @@ func TestFlagSetting(t *testing.T) { {[]string{"@tls://dns.google"}}, {[]string{"@https://dns.cloudflare.com/dns-query"}}, {[]string{"@quic://dns.adguard.com"}}, + {[]string{"@tcp://dns.froth.zone"}}, + {[]string{"@udp://dns.example.com"}}, } for i, test := range tests { @@ -147,6 +149,10 @@ func TestFlagSetting(t *testing.T) { assert.Assert(t, opts.HTTPS) case 3: assert.Assert(t, opts.QUIC) + case 4: + assert.Assert(t, opts.TCP) + case 5: + assert.Assert(t, true) } }) } diff --git a/completions/zsh.zsh b/completions/zsh.zsh index 8463dbf..64afd0c 100644 --- a/completions/zsh.zsh +++ b/completions/zsh.zsh @@ -13,6 +13,7 @@ local -a alts args '*+'{no,}'aaonly[set aa flag in the query]' '*+'{no,}'additional[print additional section of a reply]' '*+'{no,}'adflag[set the AD (authentic data) bit in the query]' + '*+'{no,}'badcookie[retry BADCOOKIE responses]' '*+'{no,}'cdflag[set the CD (checking disabled) bit in the query]' '*+'{no,}'cookie[add a COOKIE option to the request]' '*+edns=[specify EDNS version for query]:version (0-255)' diff --git a/doc/awl.1.scd b/doc/awl.1.scd index bc0110a..de88719 100644 --- a/doc/awl.1.scd +++ b/doc/awl.1.scd @@ -80,6 +80,9 @@ Anything in [brackets] is optional. *--no-truncate*, *+ignore* Ignore UDP truncation (by default, awl *retries with TCP*). +*--no-bad-cookie*, *+[no]badcookie* + \[Do not\] ignore BADCOOKIE responses + *--tcp*, *+tcp*, *+vc* Use TCP for the query (see RFC 7766). diff --git a/go.mod b/go.mod index 1fa792b..fd8f97d 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/miekg/dns v1.1.50 github.com/stefansundin/go-zflag v1.1.1 golang.org/x/net v0.0.0-20220909164309-bea034e7d591 - golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 + golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 gopkg.in/yaml.v3 v3.0.1 gotest.tools/v3 v3.3.0 ) @@ -19,7 +19,7 @@ require ( github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect github.com/ameshkov/dnsstamps v1.0.3 // indirect - github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/golang/mock v1.6.0 // indirect github.com/google/go-cmp v0.5.8 // indirect @@ -27,9 +27,9 @@ require ( github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect github.com/nxadm/tail v1.4.8 // indirect - github.com/onsi/ginkgo v1.16.4 // indirect - golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d // indirect - golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect + github.com/onsi/ginkgo v1.16.5 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/exp v0.0.0-20220914170420-dc92f8653013 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/tools v0.1.12 // indirect diff --git a/go.sum b/go.sum index 4f95d5f..f40c575 100644 --- a/go.sum +++ b/go.sum @@ -15,8 +15,9 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs 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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= @@ -56,8 +57,8 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= @@ -76,10 +77,10 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo= -golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20220914170420-dc92f8653013 h1:ZjglnWxEUdPyXl4o/j4T89SRCI+4X6NW6185PNLEOF4= +golang.org/x/exp v0.0.0-20220914170420-dc92f8653013/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= @@ -113,8 +114,9 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM= -golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/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-20220915200043-7b5979e65e41 h1:ohgcoMbSofXygzo6AD2I1kz3BFmW1QArPYTtwEM3UXc= +golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/main.go b/main.go index 494e786..b164e61 100644 --- a/main.go +++ b/main.go @@ -17,7 +17,7 @@ import ( var version = "DEV" func main() { - if opts, code, err := run(); err != nil { + if opts, code, err := run(os.Args); err != nil { // TODO: Make not ew if errors.Is(err, cli.ErrNotError) || strings.Contains(err.Error(), "help requested") { os.Exit(0) @@ -28,8 +28,8 @@ func main() { } } -func run() (opts util.Options, code int, err error) { - opts, err = cli.ParseCLI(version) +func run(args []string) (opts util.Options, code int, err error) { + opts, err = cli.ParseCLI(args, version) if err != nil { return opts, 1, fmt.Errorf("parse: %w", err) } @@ -53,7 +53,7 @@ func run() (opts util.Options, code int, err error) { var str string if opts.JSON || opts.XML || opts.YAML { - str, err = query.PrintSpecial(resp.DNS, opts) + str, err = query.PrintSpecial(resp, opts) if err != nil { return opts, 10, fmt.Errorf("format print: %w", err) } diff --git a/main_test.go b/main_test.go index eb42484..57186c9 100644 --- a/main_test.go +++ b/main_test.go @@ -14,33 +14,23 @@ func TestMain(t *testing.T) { //nolint: paralleltest // Race conditions os.Stdout = os.NewFile(0, os.DevNull) os.Stderr = os.NewFile(0, os.DevNull) - old := os.Args + args := []string{"awl", "+yaml", "@1.1.1.1"} - os.Args = []string{"awl", "+yaml", "@1.1.1.1"} - - _, code, err := run() + _, code, err := run(args) assert.NilError(t, err) assert.Equal(t, code, 0) - os.Args = []string{"awl", "+short", "@1.1.1.1"} + args = []string{"awl", "+short", "@1.1.1.1"} - _, code, err = run() + _, code, err = run(args) assert.NilError(t, err) assert.Equal(t, code, 0) - - os.Args = old } func TestHelp(t *testing.T) { - old := os.Args - os.Stdout = os.NewFile(0, os.DevNull) - os.Stderr = os.NewFile(0, os.DevNull) + args := []string{"awl", "-h"} - os.Args = []string{"awl", "-h"} - - _, code, err := run() + _, code, err := run(args) assert.ErrorIs(t, err, zflag.ErrHelp) assert.Equal(t, code, 1) - - os.Args = old } diff --git a/pkg/awl-dns-git b/pkg/awl-dns-git index c8686b1..d876d6d 160000 --- a/pkg/awl-dns-git +++ b/pkg/awl-dns-git @@ -1 +1 @@ -Subproject commit c8686b1e149b6c42db019c77caf36475aafadd03 +Subproject commit d876d6de34a78298ed041f575662015fb7eccdb5 diff --git a/pkg/logawl/docs.go b/pkg/logawl/docs.go index fa0d5f9..d9940e3 100644 --- a/pkg/logawl/docs.go +++ b/pkg/logawl/docs.go @@ -14,7 +14,7 @@ because awl is a cli utility it writes directly to std err. // You can call specific logging levels from your new logger using // // logger.Debug("Message to log") -// logger.Fatal("Message to log") +// logger.Warning("Message to log") // logger.Info("Message to log") // logger.Error("Message to log") // diff --git a/pkg/query/print.go b/pkg/query/print.go index 800fe56..d46d40d 100644 --- a/pkg/query/print.go +++ b/pkg/query/print.go @@ -18,10 +18,210 @@ import ( "gopkg.in/yaml.v3" ) +// 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 util.Response, opts util.Options) (string, error) { + if res.DNS == nil { + return " MsgHdr", errNoMessage + } + + var ( + s string + 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 { + str, err := stringParse(r.String(), false, opts) + if err != nil { + return "", fmt.Errorf("%w", err) + } + + s += str + "\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 { + str, err := stringParse(r.String(), true, opts) + if err != nil { + return "", fmt.Errorf("%w", err) + } + + s += str + "\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 { + str, err := stringParse(r.String(), true, opts) + if err != nil { + return "", fmt.Errorf("%w", err) + } + + s += str + "\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 { + str, err := stringParse(r.String(), true, opts) + if err != nil { + return "", fmt.Errorf("%w", err) + } + + s += str + "\n" + } + } + } + } + + if opts.Display.Statistics { + s += "\n;; Query time: " + res.RTT.String() + s += "\n;; SERVER: " + opts.Request.Server + serverExtra(opts) + 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, nil +} + +func serverExtra(opts util.Options) string { + // Add extra information to server string + var extra string + + switch { + case opts.TCP: + extra = ":" + strconv.Itoa(opts.Request.Port) + " (TCP)" + case opts.TLS: + extra = ":" + strconv.Itoa(opts.Request.Port) + " (TLS)" + case opts.HTTPS, opts.DNSCrypt: + extra = "" + case opts.QUIC: + extra = ":" + strconv.Itoa(opts.Request.Port) + " (QUIC)" + default: + extra = ":" + strconv.Itoa(opts.Request.Port) + " (UDP)" + } + + return extra +} + +// stringParse edits the raw responses to user requests. +func stringParse(str string, isAns bool, opts util.Options) (string, error) { + split := strings.Split(str, "\t") + + // Make edits if so requested + + // TODO: make less ew? + // This exists because the question section should be left alone EXCEPT for punycode. + + if isAns { + if !opts.Display.TTL { + // Remove from existence + split = append(split[:1], split[2:]...) + } + + if !opts.Display.ShowClass { + // Position depends on if the TTL is there or not. + if opts.Display.TTL { + split = append(split[:2], split[3:]...) + } else { + split = append(split[:1], split[2:]...) + } + } + + if opts.Display.TTL && opts.Display.HumanTTL { + ttl, _ := strconv.Atoi(split[1]) + split[1] = (time.Duration(ttl) * time.Second).String() + } + } + + if opts.Display.UcodeTranslate { + var ( + err error + semi string + ) + + if strings.HasPrefix(split[0], ";") { + split[0] = strings.TrimPrefix(split[0], ";") + semi = ";" + } + + split[0], err = idna.ToUnicode(split[0]) + if err != nil { + return "", fmt.Errorf("punycode: %w", err) + } + + split[0] = semi + split[0] + } + + return strings.Join(split, "\t"), nil +} + // 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) +func PrintSpecial(res util.Response, opts util.Options) (string, error) { + formatted, err := MakePrintable(res, opts) if err != nil { return "", err } @@ -52,9 +252,11 @@ func PrintSpecial(msg *dns.Msg, opts util.Options) (string, error) { // 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 util.Options) (*Message, error) { - var err error - +func MakePrintable(res util.Response, opts util.Options) (*Message, error) { + var ( + err error + msg = res.DNS + ) // The things I do for compatibility ret := Message{ Header: Header{ @@ -72,6 +274,14 @@ func MakePrintable(msg *dns.Msg, opts util.Options) (*Message, error) { }, } + opt := msg.IsEdns0() + if opt != nil && opts.Display.Opt { + ret.Opt, err = parseOpt(*opt) + if err != nil { + return nil, fmt.Errorf("edns print: %w", err) + } + } + for _, question := range msg.Question { var name string if opts.Display.UcodeTranslate { @@ -205,12 +415,15 @@ func MakePrintable(msg *dns.Msg, opts util.Options) (*Message, error) { } } - opt := msg.IsEdns0() - if opt != nil && opts.Display.Opt { - ret.Opt, err = parseOpt(*opt) - if err != nil { - return nil, fmt.Errorf("edns print: %w", err) + if opts.Display.Statistics { + ret.Statistics = Statistics{ + RTT: res.RTT.String(), + Server: opts.Request.Server + serverExtra(opts), + When: time.Now().Format(time.RFC1123Z), + MsgSize: res.DNS.Len(), } + } else { + ret.Statistics = Statistics{} } return &ret, nil diff --git a/pkg/query/print_test.go b/pkg/query/print_test.go index d80a2f2..c0bb505 100644 --- a/pkg/query/print_test.go +++ b/pkg/query/print_test.go @@ -193,7 +193,7 @@ func TestRealPrint(t *testing.T) { if test.JSON || test.XML || test.YAML { str := "" - str, err = query.PrintSpecial(resp.DNS, test) + str, err = query.PrintSpecial(resp, test) assert.NilError(t, err) assert.Assert(t, str != "") } @@ -207,7 +207,7 @@ func TestRealPrint(t *testing.T) { func TestBadFormat(t *testing.T) { t.Parallel() - _, err := query.PrintSpecial(new(dns.Msg), util.Options{}) + _, err := query.PrintSpecial(util.Response{DNS: new(dns.Msg)}, util.Options{}) assert.ErrorContains(t, err, "never happen") } diff --git a/pkg/query/query.go b/pkg/query/query.go index 729db25..c048130 100644 --- a/pkg/query/query.go +++ b/pkg/query/query.go @@ -5,211 +5,17 @@ package query import ( "fmt" "strconv" - "strings" - "time" "git.froth.zone/sam/awl/pkg/resolvers" "git.froth.zone/sam/awl/pkg/util" "github.com/dchest/uniuri" "github.com/miekg/dns" - "golang.org/x/net/idna" ) -// 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 util.Response, opts util.Options) (string, error) { - if res.DNS == nil { - return " MsgHdr", errNoMessage - } - - var ( - s string - 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 { - str, err := stringParse(r.String(), false, opts) - if err != nil { - return "", fmt.Errorf("%w", err) - } - - s += str + "\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 { - str, err := stringParse(r.String(), true, opts) - if err != nil { - return "", fmt.Errorf("%w", err) - } - - s += str + "\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 { - str, err := stringParse(r.String(), true, opts) - if err != nil { - return "", fmt.Errorf("%w", err) - } - - s += str + "\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 { - str, err := stringParse(r.String(), true, opts) - if err != nil { - return "", fmt.Errorf("%w", err) - } - - s += str + "\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.Request.Port) + " (TCP)" - case opts.TLS: - extra = ":" + strconv.Itoa(opts.Request.Port) + " (TLS)" - case opts.HTTPS, opts.DNSCrypt: - extra = "" - case opts.QUIC: - extra = ":" + strconv.Itoa(opts.Request.Port) + " (QUIC)" - default: - extra = ":" + strconv.Itoa(opts.Request.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, nil -} - -// stringParse edits the raw responses to user requests. -func stringParse(str string, isAns bool, opts util.Options) (string, error) { - split := strings.Split(str, "\t") - - // Make edits if so requested - - // TODO: make less ew? - // This exists because the question section should be left alone EXCEPT for punycode. - - if isAns { - if !opts.Display.TTL { - // Remove from existence - split = append(split[:1], split[2:]...) - } - - if !opts.Display.ShowClass { - // Position depends on if the TTL is there or not. - if opts.Display.TTL { - split = append(split[:2], split[3:]...) - } else { - split = append(split[:1], split[2:]...) - } - } - - if opts.Display.TTL && opts.Display.HumanTTL { - ttl, _ := strconv.Atoi(split[1]) - split[1] = (time.Duration(ttl) * time.Second).String() - } - } - - if opts.Display.UcodeTranslate { - var ( - err error - semi string - ) - - if strings.HasPrefix(split[0], ";") { - split[0] = strings.TrimPrefix(split[0], ";") - semi = ";" - } - - split[0], err = idna.ToUnicode(split[0]) - if err != nil { - return "", fmt.Errorf("punycode: %w", err) - } - - split[0] = semi + split[0] - } - - return strings.Join(split, "\t"), nil -} +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. @@ -230,64 +36,64 @@ func CreateQuery(opts util.Options) (util.Response, error) { // EDNS time :) if opts.EDNS.EnableEDNS { - o := new(dns.OPT) - o.Hdr.Name = "." - o.Hdr.Rrtype = dns.TypeOPT + edns := new(dns.OPT) + edns.Hdr.Name = "." + edns.Hdr.Rrtype = dns.TypeOPT - o.SetVersion(opts.EDNS.Version) + edns.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) + cookie := new(dns.EDNS0_COOKIE) + cookie.Code = dns.EDNS0COOKIE + cookie.Cookie = uniuri.NewLenChars(16, []byte("1234567890abcdef")) + edns.Option = append(edns.Option, cookie) - opts.Logger.Info("Setting EDNS cookie to", e.Cookie) + opts.Logger.Info("Setting EDNS cookie to", cookie.Cookie) } if opts.EDNS.Expire { - o.Option = append(o.Option, new(dns.EDNS0_EXPIRE)) + edns.Option = append(edns.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)) + edns.Option = append(edns.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)) + edns.Option = append(edns.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)) + edns.Option = append(edns.Option, new(dns.EDNS0_PADDING)) opts.Logger.Info("Setting EDNS padding") } - o.SetUDPSize(opts.EDNS.BufSize) + edns.SetUDPSize(opts.EDNS.BufSize) opts.Logger.Info("EDNS UDP buffer set to", opts.EDNS.BufSize) - o.SetZ(opts.EDNS.ZFlag) + edns.SetZ(opts.EDNS.ZFlag) opts.Logger.Info("EDNS Z flag set to", opts.EDNS.ZFlag) if opts.EDNS.DNSSEC { - o.SetDo() + edns.SetDo() opts.Logger.Info("EDNS DNSSEC OK set") } if opts.EDNS.Subnet.Address != nil { - o.Option = append(o.Option, &opts.EDNS.Subnet) + edns.Option = append(edns.Option, &opts.EDNS.Subnet) } - req.Extra = append(req.Extra, o) + req.Extra = append(req.Extra, edns) } else if opts.EDNS.DNSSEC { req.SetEdns0(1232, true) opts.Logger.Warn("DNSSEC implies EDNS, EDNS enabled") @@ -306,7 +112,7 @@ func CreateQuery(opts util.Options) (util.Response, error) { ) if opts.JSON || opts.XML || opts.YAML { - str, err = PrintSpecial(req, opts) + str, err = PrintSpecial(util.Response{DNS: req}, opts) if err != nil { return util.Response{}, err } diff --git a/pkg/query/query_test.go b/pkg/query/query_test.go index fce2cf3..042f25d 100644 --- a/pkg/query/query_test.go +++ b/pkg/query/query_test.go @@ -117,7 +117,7 @@ func TestCreateQ(t *testing.T) { assert.NilError(t, err) assert.Assert(t, res != util.Response{}) - str, err := query.PrintSpecial(res.DNS, opt) + str, err := query.PrintSpecial(res, opt) assert.NilError(t, err) assert.Assert(t, str != "") diff --git a/pkg/query/struct.go b/pkg/query/struct.go index a4199e3..3ad2159 100644 --- a/pkg/query/struct.go +++ b/pkg/query/struct.go @@ -11,7 +11,7 @@ import ( //nolint:govet // Better looking output is worth a few bytes. type Message struct { // Header section - Header Header `json:"header,omitempty" xml:"header,omitempty" yaml:"header,omitempty"` + Header `json:"header,omitempty" xml:"header,omitempty" yaml:"header,omitempty"` // Opt Pseudosection Opt []Opts `json:"opt,omitempty" xml:"opt,omitempty" yaml:"opt,omitempty"` // Question Section @@ -22,42 +22,44 @@ type Message struct { Authority []Answer `json:"authority,omitempty" xml:"authority,omitempty" yaml:"authority,omitempty"` // Additional Section Additional []Answer `json:"additional,omitempty" xml:"additional,omitempty" yaml:"additional,omitempty"` + // Statistics :) + Statistics `json:"statistics,omitempty" xml:"statistics,omitempty" yaml:"statistics,omitempty"` } // Header is the header. type Header struct { - Opcode string `json:"opcode," xml:"opcode," yaml:"opcode"` - Status string `json:"status," xml:"status," yaml:"status"` - ID uint16 `json:"id," xml:"id," yaml:"id"` - Response bool `json:"response," xml:"response," yaml:"response"` - Authoritative bool `json:"authoritative," xml:"authoritative," yaml:"authoritative"` - Truncated bool `json:"truncated," xml:"truncated," yaml:"truncated"` - RecursionDesired bool `json:"recursionDesired," xml:"recursionDesired," yaml:"recursionDesired"` - RecursionAvailable bool `json:"recursionAvailable," xml:"recursionAvailable," yaml:"recursionAvailable"` - Zero bool `json:"zero," xml:"zero," yaml:"zero"` - AuthenticatedData bool `json:"authenticatedData," xml:"authenticatedData," yaml:"authenticatedData"` - CheckingDisabled bool `json:"checkingDisabled," xml:"checkingDisabled," yaml:"checkingDisabled"` + Opcode string `json:"opcode," xml:"opcode," yaml:"opcode" example:"QUERY"` + Status string `json:"status," xml:"status," yaml:"status" example:"NOERR"` + ID uint16 `json:"id," xml:"id," yaml:"id" example:"12"` + Response bool `json:"response," xml:"response," yaml:"response" example:"true"` + Authoritative bool `json:"authoritative," xml:"authoritative," yaml:"authoritative" example:"false"` + Truncated bool `json:"truncated," xml:"truncated," yaml:"truncated" example:"false"` + RecursionDesired bool `json:"recursionDesired," xml:"recursionDesired," yaml:"recursionDesired" example:"true"` + RecursionAvailable bool `json:"recursionAvailable," xml:"recursionAvailable," yaml:"recursionAvailable" example:"true"` + Zero bool `json:"zero," xml:"zero," yaml:"zero" example:"false"` + AuthenticatedData bool `json:"authenticatedData," xml:"authenticatedData," yaml:"authenticatedData" example:"false"` + CheckingDisabled bool `json:"checkingDisabled," xml:"checkingDisabled," yaml:"checkingDisabled" example:"false"` } // Question is a DNS Query. type Question struct { - Name string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty"` - Class string `json:"class,omitempty" xml:"class,omitempty" yaml:"class,omitempty"` - Type string `json:"type,omitempty" xml:"type,omitempty" yaml:"type,omitempty"` + Name string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty" example:"localhost"` + Class string `json:"class,omitempty" xml:"class,omitempty" yaml:"class,omitempty" example:"A"` + Type string `json:"type,omitempty" xml:"type,omitempty" yaml:"type,omitempty" example:"IN"` } // RRHeader is for DNS Resource Headers. type RRHeader struct { - Name string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty"` - TTL string `json:"ttl,omitempty" xml:"ttl,omitempty" yaml:"ttl,omitempty"` - Class string `json:"class,omitempty" xml:"class,omitempty" yaml:"class,omitempty"` - Type string `json:"type,omitempty" xml:"type,omitempty" yaml:"type,omitempty"` + Name string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty" example:"127.0.0.1"` + TTL string `json:"ttl,omitempty" xml:"ttl,omitempty" yaml:"ttl,omitempty" example:"0ms"` + Class string `json:"class,omitempty" xml:"class,omitempty" yaml:"class,omitempty" example:"A"` + Type string `json:"type,omitempty" xml:"type,omitempty" yaml:"type,omitempty" example:"IN"` Rdlength uint16 `json:"-" xml:"-" yaml:"-"` } // Opts is for the OPT pseudosection, nearly exclusively for EDNS. type Opts struct { - Name string `json:"name,omitempty" xml:"name,omitempty" yaml:",omitempty"` + Name string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty"` Value string `json:"value" xml:"value" yaml:"value"` } @@ -67,4 +69,12 @@ type Answer struct { RRHeader `json:"header,omitempty" xml:"header,omitempty" yaml:"header,omitempty"` } +// Statistics is the little bit at the bottom :). +type Statistics struct { + RTT string `json:"queryTime,omitempty" xml:"queryTime,omitempty" yaml:"queryTime,omitempty"` + Server string `json:"server,omitempty" xml:"server,omitempty" yaml:"server,omitempty"` + When string `json:"when,omitempty" xml:"when,omitempty" yaml:"when,omitempty"` + MsgSize int `json:"msgSize,omitempty" xml:"msgSize,omitempty" yaml:"msgSize,omitempty"` +} + var errNoMessage = errors.New("no message") diff --git a/pkg/resolvers/general.go b/pkg/resolvers/general.go index 88353af..1f94c82 100644 --- a/pkg/resolvers/general.go +++ b/pkg/resolvers/general.go @@ -52,13 +52,31 @@ func (resolver *StandardResolver) LookUp(msg *dns.Msg) (util.Response, error) { } } - resolver.opts.Logger.Debug("Using", dnsClient.Net, "for making the request") + resolver.opts.Logger.Info("Using", dnsClient.Net, "for making the request") resp.DNS, resp.RTT, err = dnsClient.Exchange(msg, resolver.opts.Request.Server) if err != nil { return util.Response{}, fmt.Errorf("standard: DNS exchange: %w", err) } + switch dns.RcodeToString[resp.DNS.MsgHdr.Rcode] { + case "BADCOOKIE": + if !resolver.opts.BadCookie { + fmt.Printf(";; BADCOOKIE, retrying.\n\n") + + msg.Extra = resp.DNS.Extra + + resp.DNS, resp.RTT, err = dnsClient.Exchange(msg, resolver.opts.Request.Server) + + if err != nil { + return util.Response{}, fmt.Errorf("badcookie: DNS exchange: %w", err) + } + } + + case "NOERR": + break + } + resolver.opts.Logger.Info("Request successful") if resp.DNS.MsgHdr.Truncated && !resolver.opts.Truncate { diff --git a/pkg/resolvers/general_test.go b/pkg/resolvers/general_test.go index c4379bb..f9441ae 100644 --- a/pkg/resolvers/general_test.go +++ b/pkg/resolvers/general_test.go @@ -20,7 +20,7 @@ func TestResolve(t *testing.T) { Logger: util.InitLogger(0), Request: util.Request{ Server: "8.8.4.1", - Port: 53, + Port: 1, Type: dns.TypeA, Name: "example.com.", Timeout: time.Second / 2, diff --git a/pkg/util/options.go b/pkg/util/options.go index 9ae3a1f..0dbc1b0 100644 --- a/pkg/util/options.go +++ b/pkg/util/options.go @@ -13,26 +13,28 @@ import ( // Options is the grand structure for all query options. type Options struct { // The logger - Logger *logawl.Logger + Logger *logawl.Logger `json:"-"` // Host to verify TLS cert with - TLSHost string `json:"tlsHost"` + TLSHost string `json:"tlsHost" example:""` // EDNS Options EDNS // DNS request :) Request Request // Verbosity levels, see [logawl.AllLevels] - Verbosity int `json:"-"` + Verbosity int `json:"-" example:"0"` // Display options Display Display // Ignore Truncation - Truncate bool `json:"ignoreTruncate"` + Truncate bool `json:"ignoreTruncate" example:"false"` + // Ignore BADCOOKIE + BadCookie bool `json:"ignoreBadCookie" example:"false"` // Print only the answer - Short bool `json:"short"` + Short bool `json:"short" example:"false"` // When Short is true, display where the query came from - Identify bool `json:"identify"` + Identify bool `json:"identify" example:"false"` // Perform a reverse DNS query when true - Reverse bool `json:"reverse"` + Reverse bool `json:"reverse" example:"false"` HeaderFlags @@ -44,42 +46,43 @@ type Options struct { YAML bool `json:"-" xml:"-" yaml:"-"` // Use TCP instead of UDP to make the query - TCP bool `json:"tcp"` + TCP bool `json:"tcp" example:"false"` // Use DNS-over-TLS to make the query - TLS bool `json:"dns_over_tls"` + TLS bool `json:"dnsOverTLS" example:"false"` // When using TLS, ignore certificates - TLSNoVerify bool `json:"tlsNoVerify"` + TLSNoVerify bool `json:"tlsNoVerify" example:"false"` // Use DNS-over-HTTPS to make the query - HTTPS bool `json:"dns_over_https"` + HTTPS bool `json:"dnsOverHTTPS" example:"false"` // Use DNS-over-QUIC to make the query - QUIC bool `json:"dns_over_quic"` + //nolint:tagliatelle // QUIC is an acronym + QUIC bool `json:"dnsOverQUIC" example:"false"` // Use DNSCrypt to make the query - DNSCrypt bool `json:"dnscrpyt"` + DNSCrypt bool `json:"dnscrypt" example:"false"` // Force IPv4 only - IPv4 bool `json:"force_IPv4"` + IPv4 bool `json:"forceIPv4" example:"false"` // Force IPv6 only - IPv6 bool `json:"force_IPv6"` + IPv6 bool `json:"forceIPv6" example:"false"` } // HeaderFlags are the flags that are in DNS headers. type HeaderFlags struct { // Authoritative Answer DNS query flag - AA bool `json:"authoritative,"` + AA bool `json:"authoritative" example:"false"` // Authenticated Data DNS query flag - AD bool `json:"authenticatedData,"` + AD bool `json:"authenticatedData" example:"false"` // Checking Disabled DNS query flag - CD bool `json:"checkingDisabled,"` + CD bool `json:"checkingDisabled" example:"false"` // QueRy DNS query flag - QR bool `json:"query"` + QR bool `json:"query" example:"false"` // Recursion Desired DNS query flag - RD bool `json:"recursionDesired"` + RD bool `json:"recursionDesired" example:"true"` // Recursion Available DNS query flag - RA bool `json:"recursionAvailable"` + RA bool `json:"recursionAvailable" example:"false"` // TrunCated DNS query flag - TC bool `json:"truncated"` + TC bool `json:"truncated" example:"false"` // Zero DNS query flag - Z bool `json:"zero"` + Z bool `json:"zero" example:"false"` } // Display contains toggles for what to (and not to) display. @@ -87,32 +90,32 @@ type Display struct { /* Section displaying */ // Comments? - Comments bool `json:"comments"` + Comments bool `json:"comments" example:"true"` // QUESTION SECTION - Question bool `json:"question"` + Question bool `json:"question" example:"true"` // OPT PSEUDOSECTION - Opt bool `json:"opt"` + Opt bool `json:"opt" example:"true"` // ANSWER SECTION - Answer bool `json:"answer"` + Answer bool `json:"answer" example:"true"` // AUTHORITY SECTION - Authority bool `json:"authority"` + Authority bool `json:"authority" example:"true"` // ADDITIONAL SECTION - Additional bool `json:"additional"` + Additional bool `json:"additional" example:"true"` // Query time, message size, etc. - Statistics bool `json:"statistics"` + Statistics bool `json:"statistics" example:"true"` // Display TTL in response - TTL bool `json:"ttl"` + TTL bool `json:"ttl" example:"true"` /* Answer formatting */ // Display Class in response - ShowClass bool `json:"showClass"` + ShowClass bool `json:"showClass" example:"true"` // Display query before it is sent - ShowQuery bool `json:"showQuery"` + ShowQuery bool `json:"showQuery" example:"false"` // Display TTL as human-readable - HumanTTL bool `json:"HumanTTL"` + HumanTTL bool `json:"humanTTL" example:"false"` // Translate Punycode back to Unicode - UcodeTranslate bool `json:"unicode"` + UcodeTranslate bool `json:"unicode" example:"true"` } // EDNS contains toggles for various EDNS options. @@ -120,25 +123,25 @@ type EDNS struct { // Subnet to originate query from. Subnet dns.EDNS0_SUBNET `json:"subnet"` // Must Be Zero flag - ZFlag uint16 `json:"zflag"` + ZFlag uint16 `json:"zflag" example:"0"` // UDP buffer size - BufSize uint16 `json:"bufSize"` + BufSize uint16 `json:"bufSize" example:"1232"` // Enable/Disable EDNS entirely - EnableEDNS bool `json:"edns"` + EnableEDNS bool `json:"edns" example:"false"` // Sending EDNS cookie - Cookie bool `json:"cookie"` + Cookie bool `json:"cookie" example:"true"` // Enabling DNSSEC - DNSSEC bool `json:"dnssec"` + DNSSEC bool `json:"dnssec" example:"false"` // Sending EDNS Expire - Expire bool `json:"expire"` + Expire bool `json:"expire" example:"false"` // Sending EDNS TCP keepopen - KeepOpen bool `json:"keepOpen"` + KeepOpen bool `json:"keepOpen" example:"false"` // Sending EDNS NSID - Nsid bool `json:"nsid"` + Nsid bool `json:"nsid" example:"false"` // Send EDNS Padding - Padding bool `json:"padding"` + Padding bool `json:"padding" example:"false"` // Set EDNS version (default: 0) - Version uint8 `json:"version"` + Version uint8 `json:"version" example:"0"` } // ParseSubnet takes a subnet argument and makes it into one that the DNS library diff --git a/template.mk b/template.mk index fe820f5..41e441e 100644 --- a/template.mk +++ b/template.mk @@ -1,7 +1,7 @@ # SPDX-License-Identifier: BSD-3-Clause # Template for the BSD/GNU makefiles -HASH ?= `git describe --always --dirty --broken | sed 's/\([^-]*-g\)/r\1/;s/-/./g' || echo "UNKNOWN"` +HASH ?= `git describe --always --dirty --broken | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g' || echo "UNKNOWN"` SOURCES ?= $(shell find . -name "*.go" -type f ! -name '*_test*') TEST_SOURCES ?= $(shell find . -name "*_test.go" -type f) @@ -28,12 +28,6 @@ all: $(PROG) doc/$(PROG).1 $(PROG): $(SOURCES) $(GO) build -o $(EXE) $(GOFLAGS) . -doc/$(PROG).1: doc/$(PROG).1.scd - $(SCDOC) <$< >$@ - -doc/wiki/$(PROG).1.md: doc/$(PROG).1 - pandoc --from man --to gfm -o $@ $< - ## update_doc: update documentation (requires pandoc) update_doc: doc/wiki/$(PROG).1.md @@ -90,4 +84,4 @@ clean: .PHONY: help help: @echo "Usage: " - @sed -n 's/^##//p' $(MAKEFILE_LIST) | column -t -s ':' | sed -e 's/^/ /' \ No newline at end of file + @sed -n 's/^##//p' $(MAKEFILE_LIST) | column -t -s ':' | sed -e 's/^/ /'