From c5e55732687c6bfef1b03198426661d7ff325036 Mon Sep 17 00:00:00 2001 From: Sam Therapy Date: Sat, 30 Jul 2022 15:32:03 +0200 Subject: [PATCH 1/4] Preliminary EDNS Signed-off-by: Sam Therapy --- .drone.jsonnet | 31 ++++-- .drone.yml | 124 +++++++++++++++++++++ .github/ISSUE_TEMPLATE/bug.md | 28 +++++ .github/ISSUE_TEMPLATE/feature.md | 18 +++ .github/pull_request_template.md | 9 ++ .gitignore | 2 + GNUmakefile | 3 +- LICENSE => LICENCE | 0 README.md | 4 +- cli/cli.go | 88 +++++++++------ cli/cli_test.go | 8 +- cli/dig.go | 129 ++++++++++++++++------ cli/dig_test.go | 18 ++- cli/options.go | 47 ++++++-- doc/awl.1 | 90 ++++++++++++--- go.mod | 7 +- go.sum | 13 +-- main.go | 93 ++-------------- main_test.go | 14 +++ mkfile | 10 +- query/print.go | 175 ++++++++++++++++++++++++++++++ query/query.go | 113 ++++++++++++++++--- query/query_test.go | 76 ++++++++++++- query/struct.go | 148 +++++++++++++++++++++++++ query/struct_test.go | 14 +++ template.mk | 16 +-- 26 files changed, 1041 insertions(+), 237 deletions(-) create mode 100644 .drone.yml create mode 100644 .github/ISSUE_TEMPLATE/bug.md create mode 100644 .github/ISSUE_TEMPLATE/feature.md create mode 100644 .github/pull_request_template.md rename LICENSE => LICENCE (100%) create mode 100644 main_test.go create mode 100644 query/print.go create mode 100644 query/struct.go create mode 100644 query/struct_test.go diff --git a/.drone.jsonnet b/.drone.jsonnet index 53094a7..5e673fd 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -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,9 +79,6 @@ local release() = { commands: [ "goreleaser release" ], - // when: { - // event: "tag" - // } } ] }; @@ -77,5 +86,7 @@ local release() = { [ testing("1.18", "amd64"), testing("1.18", "arm64"), + + release() ] \ No newline at end of file diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..1599481 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,124 @@ +--- +{ + "kind": "pipeline", + "name": "1.18-amd64", + "platform": { + "arch": "amd64" + }, + "steps": [ + { + "commands": [ + "git submodule update --init --recursive" + ], + "image": "alpine/git", + "name": "submodules" + }, + { + "depends_on": [ + "submodules" + ], + "image": "rancher/drone-golangci-lint:latest", + "name": "lint" + }, + { + "commands": [ + "go test -v -race ./... -cover" + ], + "depends_on": [ + "submodules" + ], + "image": "golang:1.18", + "name": "test" + } + ], + "trigger": { + "event": { + "exclude": [ + "tag" + ] + } + }, + "type": "docker" +} +--- +{ + "kind": "pipeline", + "name": "1.18-arm64", + "platform": { + "arch": "arm64" + }, + "steps": [ + { + "commands": [ + "git submodule update --init --recursive" + ], + "image": "alpine/git", + "name": "submodules" + }, + { + "depends_on": [ + "submodules" + ], + "image": "rancher/drone-golangci-lint:latest", + "name": "lint" + }, + { + "commands": [ + "go test -v -race ./... -cover" + ], + "depends_on": [ + "submodules" + ], + "image": "golang:1.18", + "name": "test" + } + ], + "trigger": { + "event": { + "exclude": [ + "tag" + ] + } + }, + "type": "docker" +} +--- +{ + "kind": "pipeline", + "name": "release", + "steps": [ + { + "commands": [ + "git fetch --tags", + "git submodule update --init --recursive" + ], + "image": "alpine/git", + "name": "fetch" + }, + { + "commands": [ + "go test -race ./... -cover" + ], + "image": "golang", + "name": "test" + }, + { + "commands": [ + "goreleaser release" + ], + "environment": { + "GITEA_TOKEN": { + "from_secret": "GITEA_TOKEN" + } + }, + "image": "goreleaser/goreleaser", + "name": "release" + } + ], + "trigger": { + "event": [ + "tag" + ] + }, + "type": "docker" +} diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 0000000..a587df9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -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. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md new file mode 100644 index 0000000..56de423 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -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. \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..76f377f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/.gitignore b/.gitignore index be06a1f..011681a 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ dist/ # Test coverage coverage/* !coverage/.gitkeep + +awl diff --git a/GNUmakefile b/GNUmakefile index 7abf056..fc8a3cc 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -13,10 +13,11 @@ $(PROG): $(GO) build -o $(EXE) $(GOFLAGS) . ## install: installs awl -install: all ifeq ($(OS),Windows_NT) +install: $(GO) install $(GOFLAGS) . else +install: all install -m755 $(PROG) $(PREFIX)/$(BIN) install -m644 doc/$(PROG).1 $(MAN)/man1 endif diff --git a/LICENSE b/LICENCE similarity index 100% rename from LICENSE rename to LICENCE diff --git a/README.md b/README.md index c1ca76f..580092a 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/cli/cli.go b/cli/cli.go index f14e0e2..89421b1 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -45,10 +45,21 @@ 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.Uint16("zflag", 0, "set EDNS z-flag") + 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 +76,19 @@ 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") 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") 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')) ) @@ -90,30 +102,32 @@ func ParseCLI(version string) (Options, error) { } 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 +136,25 @@ func ParseCLI(version string) (Options, error) { Retries: *retry, }, Display: Displays{ + Comments: !*noC, Question: !*noQ, 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: *zflag, + Padding: *padding, + }, } opts.Logger.Info("POSIX flags parsed") @@ -142,7 +169,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") diff --git a/cli/cli_test.go b/cli/cli_test.go index 21b9e63..087e6f4 100644 --- a/cli/cli_test.go +++ b/cli/cli_test.go @@ -16,7 +16,7 @@ func TestEmpty(t *testing.T) { 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 } @@ -26,7 +26,7 @@ func TestTLSPort(t *testing.T) { 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 } @@ -42,7 +42,7 @@ 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: unknown flag") os.Args = old } @@ -92,7 +92,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") }) diff --git a/cli/dig.go b/cli/dig.go index 32b4193..60e0b56 100644 --- a/cli/dig.go +++ b/cli/dig.go @@ -13,86 +13,112 @@ import ( 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 "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.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"): + case strings.HasPrefix(arg, "time"), strings.HasPrefix(arg, "timeout"): timeout, err := strconv.Atoi(strings.Split(arg, "=")[1]) if err != nil { - return fmt.Errorf("dig: Invalid timeout value") + return fmt.Errorf("digflags: Invalid timeout value") } opts.Request.Timeout = time.Duration(timeout) @@ -100,7 +126,7 @@ func ParseDig(arg string, opts *Options) error { 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") + return fmt.Errorf("digflags: Invalid retry value") } if strings.HasPrefix(arg, "tries") { @@ -109,8 +135,43 @@ func ParseDig(arg string, opts *Options) error { opts.Request.Retries = tries + case strings.HasPrefix(arg, "bufsize"): + size, err := strconv.Atoi(strings.Split(arg, "=")[1]) + if err != nil { + return fmt.Errorf("digflags: Invalid UDP buffer size value") + } + opts.EDNS.BufSize = uint16(size) + + case strings.HasPrefix(arg, "ednsflags"): + split := strings.Split(arg, "=") + if len(split) > 1 { + ver, err := strconv.ParseInt(split[1], 0, 16) + if err != nil { + return fmt.Errorf("digflags: Invalid EDNS flag") + } + // Ignore setting DO bit + opts.EDNS.ZFlag = uint16(ver & 0x7FFF) + } else { + opts.EDNS.ZFlag = 0 + } + + case strings.HasPrefix(arg, "edns"): + opts.EDNS.EnableEDNS = isNo + split := strings.Split(arg, "=") + if len(split) > 1 { + ver, err := strconv.Atoi(split[1]) + if err != nil { + return fmt.Errorf("digflags: Invalid EDNS version") + } + opts.EDNS.Version = uint8(ver) + } else { + opts.EDNS.Version = 0 + } + + case strings.HasPrefix(arg, "subnet"): + default: - return fmt.Errorf("dig: unknown flag %s given", arg) + return fmt.Errorf("digflags: unknown flag %s given", arg) } } return nil diff --git a/cli/dig_test.go b/cli/dig_test.go index 4b12db4..ed6704d 100644 --- a/cli/dig_test.go +++ b/cli/dig_test.go @@ -21,7 +21,20 @@ 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", + "cookie", "nocookeie", + "keepopen", "keepalive", "nokeepopen", "nokeepalive", + "nsid", "nonsid", + "padding", "nopadding", + "bufsize=512", "bufsize=a", + "time=5", "timeout=a", + "retry=a", "tries=3", "tcp", "vc", "notcp", "novc", "ignore", "noignore", "tls", "notls", @@ -29,15 +42,18 @@ func FuzzDig(f *testing.F) { "https", "nohttps", "quic", "noquic", "short", "noshort", + "identify", "noidentify", "json", "nojson", "xml", "noxml", "yaml", "noyaml", + "comments", "nocomments", "question", "noquestion", "answer", "noanswer", "authority", "noauthority", "additional", "noadditional", "stats", "nostats", "all", "noall", + "idnout", "noidnout", "invalid", } for _, tc := range seeds { @@ -49,7 +65,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:") } }) } diff --git a/cli/options.go b/cli/options.go index 16d0978..1e51cab 100644 --- a/cli/options.go +++ b/cli/options.go @@ -15,13 +15,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 +32,47 @@ 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 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 + 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 EDNS_SUBNET +} + +// Subnets get their own structure because complicated +// type EDNS_SUBNET struct { +// family uint16 +// } + var ErrNotError = errors.New("not an error") diff --git a/doc/awl.1 b/doc/awl.1 index 3145c0f..7a11969 100644 --- a/doc/awl.1 +++ b/doc/awl.1 @@ -5,7 +5,7 @@ .nh .ad l .\" Begin generated content: -.TH "awl" "1" "2022-07-25" +.TH "awl" "1" "2022-07-29" .PP .SH NAME awl - DNS lookup tool @@ -23,8 +23,8 @@ where .PP .SH DESCRIPTION .PP -\fBawl\fR (\fBa\fRwls \fBw\fRant \fBl\fRicorice) is a simple tool designed to make DNS queries, -much like the venerable \fIdig\fR(1).\& An awl is a tool used to make small holes, +\fBawl\fR (\fBa\fRwls \fBw\fRant \fBl\fRicorice) is a simple tool designed to make DNS queries +, much like the venerable \fIdig\fR(1).\& An awl is a tool used to make small holes, typically used in leatherworking.\& .PP \fBawl\fR is designed to be a more "modern" version of \fIdrill\fR(1) by including @@ -39,6 +39,10 @@ Dig-like +[no]flags are supported, see dig(1) .br Enable DNSSEC.\& This needs to be manually enabled.\& .PP +\fB--qr\fR, \fB+qr\fR +.br + Print query before sending.\& +.PP \fB-v\fR \fIvalue\fR .br Set verbosity (currently WIP) @@ -128,7 +132,7 @@ Dig-like +[no]flags are supported, see dig(1) .br 0.\&5 seconds is the minimum.\& .PP -\fB--retries\fR \fIint\fR, \fB+tries\fR=\fIint\fR, \fB+ retry\fR=\fIint\fR +\fB--retries\fR \fIint\fR, \fB+tries\fR=\fIint\fR, \fB+retry\fR=\fIint\fR .br Set the number of retries.\& .br @@ -138,61 +142,105 @@ Dig-like +[no]flags are supported, see dig(1) .SS DNS Flags .PP .RS 4 -\fB--aa=[false]\fR, \fB+[no]aaflag\fR +\fB--aa\fR[\fI=false\fR], \fB+[no]aaflag\fR .br (Set, Unset) AA (Authoritative Answer) flag .PP -\fB--ad=[false]\fR, \fB+[no]adflag\fR +\fB--ad\fR[\fI=false\fR], \fB+[no]adflag\fR .br (Set, Unset) AD (Authenticated Data) flag .PP -\fB--tc=[false]\fR, \fB+[no]tcflag\fR +\fB--tc\fR[\fI=false\fR], \fB+[no]tcflag\fR .br (Set, Unset) TC (TrunCated) flag .PP -\fB-z=[false]\fR, \fB+[no]zflag\fR +\fB-z\fR[\fI=false\fR], \fB+[no]zflag\fR .br (Set, Unset) Z (Zero) flag .PP -\fB--cd=[false]\fR, \fB+[no]cdflag\fR +\fB--cd\fR[\fI=false\fR], \fB+[no]cdflag\fR .br (Set, Unset) CD (Checking Disabled) flag .PP -\fB--qr=[false]\fR, \fB+[no]qrflag\fR +\fB--qr\fR[\fI=false\fR], \fB+[no]qrflag\fR .br (Set, Unset) QR (QueRy) flag .PP -\fB--rd=[true]\fR, \fB+[no]rdflag\fR +\fB--rd\fR[\fI=true\fR], \fB+[no]rdflag\fR .br (Set, Unset) RD (Recursion Desired) flag .PP -\fB--ra=[false]\fR, \fB+[no]raflag\fR +\fB--ra\fR[\fI=false\fR], \fB+[no]raflag\fR .br (Set, Unset) RA (Recursion Available) flag .PP .RE +.SS EDNS Flags +.RS 4 +\fB--edns-ver\fR \fIver\fR, \fB+edns\fR[\fI=ver\fR] +.br + Set EDNS version to \fIver\fR (default 0) +.PP +\fB--no-edns\fR, \fB+noedns\fR +.br + \fB--no-edns\fR and \fB+noedns\fR disable EDNS entirely +.PP +\fB--expire\fR, \fB+[no]expire\fR +.br + Add EDNS expire option to query +.PP +\fB--nsid\fR, \fB+[no]nsid\fR +.br + Add EDNS NSID option to query +.PP +\fB--no-cookie\fR, \fB+[no]cookie\fR +.br + Send an EDNS cookie (sent by default) +.PP +\fB--keep-alive\fR, \fB+[no]keepalive\fR, \fB+[no]keepopen\fR +.br + Send an EDNS TCP keepalive options +.PP +\fB--buffer-size\fR, \fB+bufsize\fR=\fIint\fR +.br + Set UDP buffer size to int (default 1232) +.PP +\fB--zflags\fR \fIflag\fR, \fB+ednsflags\fR[\fI=flag\fR] +.br + Set Z EDNS flag bits.\& Octal, decimal and hex are accepted.\& +Setting the first bit (DO) will be ignored, use \fB-D\fR instead +.PP +\fB--pad\fR, \fB+padding\fR +.br + Set EDNS padding +.PP +.RE .SS Output Display .RS 4 -\fB--no-question\fR, \fB+noquestion\fR +\fB--no-question\fR, \fB+[no]question\fR .br Do not display the Question section .PP -\fB--no-answer\fR, \fB+noanswer\fR +\fB--no-answer\fR, \fB+[no]answer\fR .br Do not display the Answer section .PP -\fB--no-answer\fR, \fB+noanswer\fR +\fB--no-answer\fR, \fB+[no]answer\fR .br Do not display the Answer section .PP -\fB--no-authority\fR, \fB+noauthority\fR +\fB--no-authority\fR, \fB+[no]authority\fR .br Do not display the Authority section .PP -\fB--no-additional\fR, \fB+noadditional\fR +\fB--no-additional\fR, \fB+[no]additional\fR .br Do not display the Additional section .PP +\fB--no-comments\fR, \fB+[no]comments\fR +.br + Do not display the query comments +.PP \fB--no-statistics\fR, \fB+nostats\fR .br Do not display the Statistics (additional comments) section @@ -241,4 +289,10 @@ awl -xT PTR 8\&.8\&.4\&.4 @dns\&.google Query dns.\&google over TLS for the PTR record to the IP address 8.\&8.\&4.\&4 .PP .SH SEE ALSO -\fIdrill\fR(1), \fIdig\fR(1), the many DNS RFCs \ No newline at end of file +\fIdrill\fR(1), \fIdig\fR(1), the many DNS RFCs +.PP +.SH BUGS +Numerous, report them either to +.br +https://git.\&froth.\&zone/sam/awl/issues or via email +~sammefishe/awl-dev@lists.\&sr.\&ht \ No newline at end of file diff --git a/go.mod b/go.mod index 604b998..64960c3 100644 --- a/go.mod +++ b/go.mod @@ -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-20220728211354-c7608f3a8462 - 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-20220730100132-1609e554cd39 + golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 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 ) diff --git a/go.sum b/go.sum index 5e8d06f..68c3834 100644 --- a/go.sum +++ b/go.sum @@ -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,10 +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= @@ -246,6 +244,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ 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= @@ -271,14 +270,10 @@ 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/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= @@ -304,6 +299,8 @@ 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= diff --git a/main.go b/main.go index 68587f0..5f8ce9e 100644 --- a/main.go +++ b/main.go @@ -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) } diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..204136b --- /dev/null +++ b/main_test.go @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package main + +import ( + "os" + "testing" +) + +func TestMain(t *testing.T) { + main() + os.Args = []string{"awl", "+yaml", "@dns.froth.zone"} + main() +} diff --git a/mkfile b/mkfile index 053cea2..14ad22b 100644 --- a/mkfile +++ b/mkfile @@ -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 . diff --git a/query/print.go b/query/print.go new file mode 100644 index 0000000..337ccd6 --- /dev/null +++ b/query/print.go @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: BSD-3-Clause + +package query + +import ( + "encoding/json" + "encoding/xml" + "fmt" + "strconv" + "strings" + "time" + + "git.froth.zone/sam/awl/cli" + "golang.org/x/net/idna" + + "github.com/miekg/dns" + "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 "", fmt.Errorf("special: no recognized format given") + } +} + +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, 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, 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, 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, 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 +} diff --git a/query/query.go b/query/query.go index 4111207..2ba34e7 100644 --- a/query/query.go +++ b/query/query.go @@ -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") + } + + 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, "\n------") + opts.QR = false + } + } resolver, err := LoadResolver(opts) if err != nil { @@ -38,5 +119,5 @@ func CreateQuery(opts cli.Options) (helpers.Response, error) { } opts.Logger.Info("Query successfully loaded") - return resolver.LookUp(res.DNS) + return resolver.LookUp(req) } diff --git a/query/query_test.go b/query/query_test.go index ab00cfa..d06c58a 100644 --- a/query/query_test.go +++ b/query/query_test.go @@ -21,12 +21,86 @@ func TestCreateQ(t *testing.T) { QR: false, Z: true, RD: false, - DNSSEC: true, + Request: helpers.Request{ Server: "8.8.4.4", Type: dns.TypeA, Name: "example.com.", }, + EDNS: cli.EDNS{ + EnableEDNS: true, + DNSSEC: true, + Cookie: true, + Expire: true, + KeepOpen: true, + Nsid: true, + }, + } + res, err := query.CreateQuery(opts) + assert.NilError(t, err) + assert.Assert(t, res != helpers.Response{}) +} + +func TestCreateQr(t *testing.T) { + opts := cli.Options{ + Logger: util.InitLogger(0), + Port: 53, + QR: false, + Z: true, + RD: false, + ShowQuery: true, + + Request: helpers.Request{ + Server: "8.8.4.4", + Type: dns.TypeA, + Name: "example.com.", + }, + EDNS: cli.EDNS{ + EnableEDNS: false, + DNSSEC: true, + Cookie: false, + Expire: false, + KeepOpen: false, + Nsid: false, + }, + Display: cli.Displays{ + Comments: true, + Question: true, + Answer: true, + Authority: true, + Additional: true, + Statistics: true, + UcodeTranslate: true, + }, + } + res, err := query.CreateQuery(opts) + assert.NilError(t, err) + assert.Assert(t, res != helpers.Response{}) +} + +func TestCreateQr2(t *testing.T) { + opts := cli.Options{ + 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.", + }, + EDNS: cli.EDNS{ + EnableEDNS: false, + DNSSEC: false, + Cookie: false, + Expire: false, + KeepOpen: false, + Nsid: false, + }, } res, err := query.CreateQuery(opts) assert.NilError(t, err) diff --git a/query/struct.go b/query/struct.go new file mode 100644 index 0000000..1c3d9ae --- /dev/null +++ b/query/struct.go @@ -0,0 +1,148 @@ +// 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" +) + +type Message struct { + Header dns.MsgHdr `json:",omitempty" xml:",omitempty" yaml:",omitempty"` + Question []Question `json:",omitempty" xml:",omitempty" yaml:",omitempty"` + Answer []Answer `json:",omitempty" xml:",omitempty" yaml:",omitempty"` + Ns []Answer `json:",omitempty" xml:",omitempty" yaml:",omitempty"` + Extra []Answer `json:",omitempty" xml:",omitempty" yaml:",omitempty"` +} + +type Question struct { + Name string `json:",omitempty" xml:",omitempty" yaml:",omitempty"` + Type string `json:",omitempty" xml:",omitempty" yaml:",omitempty"` + Class string `json:",omitempty" xml:",omitempty" yaml:",omitempty"` +} + +type RRHeader struct { + Name string `json:",omitempty" xml:",omitempty" yaml:",omitempty"` + Type string `json:",omitempty" xml:",omitempty" yaml:",omitempty"` + Class string `json:",omitempty" xml:",omitempty" yaml:",omitempty"` + TTL string `json:",omitempty" xml:",omitempty" yaml:",omitempty"` + Rdlength uint16 `json:"-" xml:"-" yaml:"-"` +} + +type Answer struct { + RRHeader `json:"Header,omitempty" xml:"Header,omitempty" yaml:"header,omitempty"` + Value string `json:"Response,omitempty" xml:"Response,omitempty" yaml:"response,omitempty"` +} + +// Turn the response into 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 " 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 { + // 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 _, res := range res.DNS.Answer { + temp := strings.Split(res.String(), "\t") + s += temp[len(temp)-1] + } + if opts.Identify { + s += " from server " + opts.Request.Server + " in " + res.RTT.String() + } + // s += "\n" + } + + return s +} diff --git a/query/struct_test.go b/query/struct_test.go new file mode 100644 index 0000000..3b2d6ba --- /dev/null +++ b/query/struct_test.go @@ -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) +} diff --git a/template.mk b/template.mk index 1698df2..e197a34 100644 --- a/template.mk +++ b/template.mk @@ -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)" PREFIX ?= /usr/local BIN ?= bin @@ -20,10 +20,10 @@ 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/$(PROG).1 2>/dev/null && rm doc/awl.bak || mv doc/awl.bak doc/awl.1 + $(SCDOC) doc/$(PROG).1 && rm doc/awl.bak || mv doc/awl.bak doc/awl.1 + ## test: run go test test: @@ -31,8 +31,8 @@ test: ## 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) -func=coverage/coverage.out + $(COVER) -html=coverage/coverage.out -o coverage/cover.html fmt: gofmt -w -s . @@ -51,6 +51,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 \ No newline at end of file -- 2.43.0 From 3a70d7f8dc3405940968b000713be1571f07cd88 Mon Sep 17 00:00:00 2001 From: Sam Therapy Date: Sat, 30 Jul 2022 15:37:00 +0200 Subject: [PATCH 2/4] Fix test Signed-off-by: Sam Therapy --- main_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/main_test.go b/main_test.go index 204136b..46aa256 100644 --- a/main_test.go +++ b/main_test.go @@ -8,7 +8,6 @@ import ( ) func TestMain(t *testing.T) { - main() - os.Args = []string{"awl", "+yaml", "@dns.froth.zone"} + os.Args = []string{"awl", "+yaml", "@1.1.1.1"} main() } -- 2.43.0 From 5ad2cdf4aed584c0a889681004009febc0dc8e2b Mon Sep 17 00:00:00 2001 From: Sam Therapy Date: Wed, 3 Aug 2022 02:11:03 +0200 Subject: [PATCH 3/4] Add subnet Signed-off-by: Sam Therapy --- .drone.jsonnet | 2 + .gitignore | 2 + GNUmakefile | 3 +- Makefile | 2 +- cli/cli.go | 47 ++++++++++++++++- cli/cli_test.go | 20 ++++++++ cli/dig.go | 126 ++++++++++++++++++++++++++++++++-------------- cli/dig_test.go | 9 ++-- cli/misc.go | 22 +++++--- cli/options.go | 32 ++++++------ conf/unix_test.go | 2 +- doc/awl.1 | 10 ++-- go.mod | 4 +- go.sum | 21 ++------ query/query.go | 6 ++- query/struct.go | 2 +- template.mk | 3 +- 17 files changed, 217 insertions(+), 96 deletions(-) diff --git a/.drone.jsonnet b/.drone.jsonnet index 5e673fd..bfd5b1c 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -84,6 +84,8 @@ local release() = { }; [ + testing("1.19", "amd64"), + testing("1.19", "arm64"), testing("1.18", "amd64"), testing("1.18", "arm64"), diff --git a/.gitignore b/.gitignore index 011681a..0aa6c52 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ coverage/* !coverage/.gitkeep awl + +.dccache \ No newline at end of file diff --git a/GNUmakefile b/GNUmakefile index fc8a3cc..c3957fd 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -12,8 +12,9 @@ endif $(PROG): $(GO) build -o $(EXE) $(GOFLAGS) . -## install: installs awl + ifeq ($(OS),Windows_NT) +## install: installs awl install: $(GO) install $(GOFLAGS) . else diff --git a/Makefile b/Makefile index d45f0fd..470145a 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/cli/cli.go b/cli/cli.go index 89421b1..d034374 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -4,8 +4,10 @@ package cli import ( "fmt" + "net" "os" "runtime" + "strconv" "strings" "time" @@ -56,7 +58,8 @@ func ParseCLI(version string) (Options, error) { cookie = flag.Bool("no-cookie", false, "disable sending EDNS cookie (default: cookie sent)") tcpKeepAlive = flag.Bool("keep-alive", false, "send EDNS TCP keep-alive") udpBufSize = flag.Uint16("buffer-size", 1232, "set EDNS UDP buffer size", flag.OptShorthand('b')) - zflag = flag.Uint16("zflag", 0, "set EDNS z-flag") + 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)") @@ -83,6 +86,7 @@ func ParseCLI(version string) (Options, error) { 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") @@ -101,6 +105,12 @@ func ParseCLI(version string) (Options, error) { return Options{Logger: util.InitLogger(*verbosity)}, err } + // TODO: DRY, dumb dumb. + mbz, err := strconv.ParseInt(*zflag, 0, 16) + if err != nil { + return Options{Logger: util.InitLogger(*verbosity)}, err + } + opts := Options{ Logger: util.InitLogger(*verbosity), Port: *port, @@ -138,6 +148,7 @@ func ParseCLI(version string) (Options, error) { Display: Displays{ Comments: !*noC, Question: !*noQ, + Opt: !*noOpt, Answer: !*noAns, Authority: !*noAuth, Additional: !*noAdd, @@ -152,11 +163,43 @@ func ParseCLI(version string) (Options, error) { Expire: *expire, KeepOpen: *tcpKeepAlive, Nsid: *nsid, - ZFlag: *zflag, + ZFlag: uint16(mbz & 0x7FFF), Padding: *padding, }, } + // TODO: DRY + if *subnet != "" { + 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), + } + } else { + return Options{Logger: util.InitLogger(*verbosity)}, err + } + } else { + sub, _ := inet.Mask.Size() + opts.EDNS.Subnet = *new(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 + } + } + } + opts.Logger.Info("POSIX flags parsed") opts.Logger.Debug(fmt.Sprintf("%+v", opts)) diff --git a/cli/cli_test.go b/cli/cli_test.go index 087e6f4..93a5a4f 100644 --- a/cli/cli_test.go +++ b/cli/cli_test.go @@ -30,6 +30,26 @@ func TestTLSPort(t *testing.T) { os.Args = old } +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 +} + func TestInvalidFlag(t *testing.T) { old := os.Args os.Args = []string{"awl", "--treebug"} diff --git a/cli/dig.go b/cli/dig.go index 60e0b56..8a09220 100644 --- a/cli/dig.go +++ b/cli/dig.go @@ -4,9 +4,12 @@ package cli import ( "fmt" + "net" "strconv" "strings" "time" + + "github.com/miekg/dns" ) // Parse dig-like commands and set the options as such. @@ -93,6 +96,8 @@ func ParseDig(arg string, opts *Options) error { opts.Display.Comments = isNo case "question": opts.Display.Question = isNo + case "opt": + opts.Display.Opt = isNo case "answer": opts.Display.Answer = isNo case "authority": @@ -104,6 +109,7 @@ func ParseDig(arg string, opts *Options) error { 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 @@ -113,41 +119,54 @@ func ParseDig(arg string, opts *Options) error { default: // Recursive switch statements WOO - switch { - case strings.HasPrefix(arg, "time"), strings.HasPrefix(arg, "timeout"): - timeout, err := strconv.Atoi(strings.Split(arg, "=")[1]) + arg := strings.Split(arg, "=") + switch arg[0] { - if err != nil { - return fmt.Errorf("digflags: Invalid timeout value") - } + case "time", "timeout": + if len(arg) > 1 && arg[1] != "" { + timeout, err := strconv.Atoi(arg[1]) - opts.Request.Timeout = time.Duration(timeout) - - case strings.HasPrefix(arg, "retry"), strings.HasPrefix(arg, "tries"): - tries, err := strconv.Atoi(strings.Split(arg, "=")[1]) - if err != nil { - return fmt.Errorf("digflags: Invalid retry value") - } - - if strings.HasPrefix(arg, "tries") { - tries++ - } - - opts.Request.Retries = tries - - case strings.HasPrefix(arg, "bufsize"): - size, err := strconv.Atoi(strings.Split(arg, "=")[1]) - if err != nil { - return fmt.Errorf("digflags: Invalid UDP buffer size value") - } - opts.EDNS.BufSize = uint16(size) - - case strings.HasPrefix(arg, "ednsflags"): - split := strings.Split(arg, "=") - if len(split) > 1 { - ver, err := strconv.ParseInt(split[1], 0, 16) if err != nil { - return fmt.Errorf("digflags: Invalid EDNS flag") + return fmt.Errorf("digflags: Invalid timeout value: %w", err) + } + + opts.Request.Timeout = time.Duration(timeout) + } else { + return fmt.Errorf("digflags: Invalid timeout value: %w", errNoArg) + } + + case "retry", "tries": + if len(arg) > 1 && arg[1] != "" { + tries, err := strconv.Atoi(arg[1]) + if err != nil { + return fmt.Errorf("digflags: Invalid retry value: %w", err) + } + opts.Request.Retries = tries + + // TODO: Is there a better way to do this? + if arg[0] == "tries" { + opts.Request.Retries++ + } + } else { + return fmt.Errorf("digflags: Invalid retry value: %w", errNoArg) + } + + case "bufsize": + if len(arg) > 1 && arg[1] != "" { + size, err := strconv.Atoi(arg[1]) + if err != nil { + return fmt.Errorf("digflags: Invalid UDP buffer size value: %w", err) + } + opts.EDNS.BufSize = uint16(size) + } else { + return fmt.Errorf("digflags: Invalid UDP buffer size value: %w", errNoArg) + } + + case "ednsflags": + if len(arg) > 1 && arg[1] != "" { + ver, err := strconv.ParseInt(arg[1], 0, 16) + if err != nil { + return fmt.Errorf("digflags: Invalid EDNS flag: %w", err) } // Ignore setting DO bit opts.EDNS.ZFlag = uint16(ver & 0x7FFF) @@ -155,20 +174,51 @@ func ParseDig(arg string, opts *Options) error { opts.EDNS.ZFlag = 0 } - case strings.HasPrefix(arg, "edns"): + case "edns": opts.EDNS.EnableEDNS = isNo - split := strings.Split(arg, "=") - if len(split) > 1 { - ver, err := strconv.Atoi(split[1]) + if len(arg) > 1 && arg[1] != "" { + ver, err := strconv.Atoi(arg[1]) if err != nil { - return fmt.Errorf("digflags: Invalid EDNS version") + return fmt.Errorf("digflags: Invalid EDNS version: %w", err) } opts.EDNS.Version = uint8(ver) } else { opts.EDNS.Version = 0 } - case strings.HasPrefix(arg, "subnet"): + case "subnet": + if len(arg) > 1 && arg[1] != "" { + ip, inet, err := net.ParseCIDR(arg[1]) + if err != nil { + // TODO: make not a default? + if arg[1] == "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 + } else { + return fmt.Errorf("digflags: Invalid EDNS Subnet: %w", errNoArg) + } + } + subnet, _ := inet.Mask.Size() + opts.EDNS.Subnet = *new(dns.EDNS0_SUBNET) + opts.EDNS.Subnet.Address = ip + opts.EDNS.Subnet.SourceNetmask = uint8(subnet) + 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 + } + } else { + return fmt.Errorf("digflags: Invalid EDNS Subnet: %w", errNoArg) + } default: return fmt.Errorf("digflags: unknown flag %s given", arg) diff --git a/cli/dig_test.go b/cli/dig_test.go index ed6704d..03e84d8 100644 --- a/cli/dig_test.go +++ b/cli/dig_test.go @@ -28,13 +28,15 @@ func FuzzDig(f *testing.F) { "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", - "time=5", "timeout=a", - "retry=a", "tries=3", + "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", @@ -48,6 +50,7 @@ func FuzzDig(f *testing.F) { "yaml", "noyaml", "comments", "nocomments", "question", "noquestion", + "opt", "noopt", "answer", "noanswer", "authority", "noauthority", "additional", "noadditional", diff --git a/cli/misc.go b/cli/misc.go index c2bbb94..6d8e1b7 100644 --- a/cli/misc.go +++ b/cli/misc.go @@ -46,23 +46,29 @@ func ParseMiscArgs(args []string, opts *Options) error { default: opts.Request.Server = arg } + + // Dig-style +queries + case strings.HasPrefix(arg, "+"): + opts.Logger.Info(arg, "detected as a dig query") + err = ParseDig(strings.ToLower(arg[1:]), opts) + if err != nil { + return err + } + + // 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 err } + + // DNS query type 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 - 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? default: opts.Logger.Info(arg, "is unknown. Assuming domain") opts.Request.Name, err = idna.ToASCII(arg) diff --git a/cli/options.go b/cli/options.go index 1e51cab..9183e38 100644 --- a/cli/options.go +++ b/cli/options.go @@ -7,6 +7,8 @@ import ( "git.froth.zone/sam/awl/internal/helpers" "git.froth.zone/sam/awl/logawl" + + "github.com/miekg/dns" ) // CLI options structure. @@ -49,6 +51,7 @@ type Options struct { type Displays struct { Comments bool Question bool // QUESTION SECTION + Opt bool // OPT PSEUDOSECTION Answer bool // ANSWER SECTION Authority bool // AUTHORITY SECTION Additional bool // ADDITIONAL SECTION @@ -57,22 +60,19 @@ type Displays struct { } 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 EDNS_SUBNET + 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) } -// Subnets get their own structure because complicated -// type EDNS_SUBNET struct { -// family uint16 -// } - var ErrNotError = errors.New("not an error") + +var errNoArg = errors.New("no argument given") diff --git a/conf/unix_test.go b/conf/unix_test.go index 9899c3a..64df984 100644 --- a/conf/unix_test.go +++ b/conf/unix_test.go @@ -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) diff --git a/doc/awl.1 b/doc/awl.1 index 7a11969..27e4a92 100644 --- a/doc/awl.1 +++ b/doc/awl.1 @@ -5,7 +5,7 @@ .nh .ad l .\" Begin generated content: -.TH "awl" "1" "2022-07-29" +.TH "awl" "1" "2022-08-01" .PP .SH NAME awl - DNS lookup tool @@ -23,11 +23,11 @@ where .PP .SH DESCRIPTION .PP -\fBawl\fR (\fBa\fRwls \fBw\fRant \fBl\fRicorice) is a simple tool designed to make DNS queries +\fBawl\fR (\fBa\fRwls \fBw\fRant \fBl\fRicorice) is a simple tool designed to make DNS queries , much like the venerable \fIdig\fR(1).\& An awl is a tool used to make small holes, typically used in leatherworking.\& .PP -\fBawl\fR is designed to be a more "modern" version of \fIdrill\fR(1) by including +\fBawl\fR is designed to be a more "modern" version of \fIdrill\fR(1) by including some more recent RFCs and output options.\& \fBawl\fR is still heavily Work-In-Progress so some features may get added or removed.\& .PP @@ -214,6 +214,10 @@ Setting the first bit (DO) will be ignored, use \fB-D\fR instead .br Set EDNS padding .PP +\fB+subnet\fR=\fIaddr/prefix\fR +.br + Set EDNS client subnet +.PP .RE .SS Output Display .RS 4 diff --git a/go.mod b/go.mod index 64960c3..cd6db6b 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( 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-20220728211354-c7608f3a8462 + golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b gopkg.in/yaml.v3 v3.0.1 gotest.tools/v3 v3.3.0 ) @@ -34,7 +34,7 @@ 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-20220728004956-3c1f35247d10 + golang.org/x/sys v0.0.0-20220731174439-a90be440212d golang.org/x/text v0.3.7 // indirect golang.org/x/tools v0.1.12 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect diff --git a/go.sum b/go.sum index 68c3834..45dfb38 100644 --- a/go.sum +++ b/go.sum @@ -219,18 +219,10 @@ 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-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= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -242,7 +234,6 @@ 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= @@ -270,10 +261,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-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-20220731174439-a90be440212d h1:Sv5ogFZatcgIMMtBSTTAgMYsicp25MXBubjXNDKwm80= +golang.org/x/sys v0.0.0-20220731174439-a90be440212d/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= @@ -297,8 +286,6 @@ 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= diff --git a/query/query.go b/query/query.go index 2ba34e7..05e2c93 100644 --- a/query/query.go +++ b/query/query.go @@ -76,6 +76,10 @@ func CreateQuery(opts cli.Options) (helpers.Response, error) { 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 { @@ -109,7 +113,7 @@ func CreateQuery(opts cli.Options) (helpers.Response, error) { str += "\n;; QUERY SIZE: " + strconv.Itoa(req.Len()) } fmt.Println(str, "\n------") - opts.QR = false + opts.ShowQuery = false } } diff --git a/query/struct.go b/query/struct.go index 1c3d9ae..f776331 100644 --- a/query/struct.go +++ b/query/struct.go @@ -60,7 +60,7 @@ func ToString(res helpers.Response, opts cli.Options) string { s += "AUTHORITY: " + strconv.Itoa(len(res.DNS.Ns)) + ", " s += "ADDITIONAL: " + strconv.Itoa(len(res.DNS.Extra)) + "\n" opt = res.DNS.IsEdns0() - if opt != nil { + if opt != nil && opts.Display.Opt { // OPT PSEUDOSECTION s += opt.String() + "\n" } diff --git a/template.mk b/template.mk index e197a34..57cfc31 100644 --- a/template.mk +++ b/template.mk @@ -7,7 +7,7 @@ VER ?= "git-$(HASH)" CGO_ENABLED ?= 0 GO ?= go COVER ?= $(GO) tool cover -GOFLAGS ?= -ldflags "-s -w -X=main.version=$(VER)" +GOFLAGS ?= -ldflags "-s -w -X=main.version=$(VER)" -trimpath PREFIX ?= /usr/local BIN ?= bin @@ -24,7 +24,6 @@ doc/$(PROG).1: doc/wiki/$(PROG).1.md @cp doc/awl.1 doc/awl.bak $(SCDOC) 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 ./... -- 2.43.0 From 49ea09a25147d183dd5a1502483d773e2d25d8eb Mon Sep 17 00:00:00 2001 From: Sam Therapy Date: Thu, 4 Aug 2022 01:29:20 +0200 Subject: [PATCH 4/4] another refactor :^) Signed-off-by: Sam Therapy --- .drone.yml | 124 --------------- cli/cli.go | 39 +---- cli/cli_test.go | 11 +- cli/dig.go | 40 +---- cli/misc.go | 24 +-- cli/misc_test.go | 2 - cli/options.go | 49 +++++- conf/plan9.go | 6 +- conf/plan9_test.go | 1 - conf/unix.go | 3 +- conf/win.go | 3 +- doc/awl.1 | 96 +++-------- docs.go | 5 +- go.mod | 2 +- go.sum | 4 - logawl/logawl.go | 8 +- logawl/logger.go | 45 +++--- logawl/logging_test.go | 16 +- main_test.go | 6 + query/DNSCrypt.go | 8 +- query/DNSCrypt_test.go | 29 +++- query/HTTPS.go | 23 ++- query/HTTPS_test.go | 1 - query/QUIC.go | 19 +-- query/QUIC_test.go | 1 - query/general.go | 11 +- query/general_test.go | 40 +++-- query/print.go | 20 +-- query/print_test.go | 343 ++++++++++++++++++++++++++++++++++++++++ query/query.go | 15 +- query/query_test.go | 164 +++++++++---------- query/resolver.go | 2 + query/struct.go | 53 ++++--- template.mk | 9 +- util/logger.go | 8 +- util/logger_test.go | 1 + util/reverseDNS.go | 17 +- util/reverseDNS_test.go | 9 +- 38 files changed, 751 insertions(+), 506 deletions(-) delete mode 100644 .drone.yml create mode 100644 query/print_test.go diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 1599481..0000000 --- a/.drone.yml +++ /dev/null @@ -1,124 +0,0 @@ ---- -{ - "kind": "pipeline", - "name": "1.18-amd64", - "platform": { - "arch": "amd64" - }, - "steps": [ - { - "commands": [ - "git submodule update --init --recursive" - ], - "image": "alpine/git", - "name": "submodules" - }, - { - "depends_on": [ - "submodules" - ], - "image": "rancher/drone-golangci-lint:latest", - "name": "lint" - }, - { - "commands": [ - "go test -v -race ./... -cover" - ], - "depends_on": [ - "submodules" - ], - "image": "golang:1.18", - "name": "test" - } - ], - "trigger": { - "event": { - "exclude": [ - "tag" - ] - } - }, - "type": "docker" -} ---- -{ - "kind": "pipeline", - "name": "1.18-arm64", - "platform": { - "arch": "arm64" - }, - "steps": [ - { - "commands": [ - "git submodule update --init --recursive" - ], - "image": "alpine/git", - "name": "submodules" - }, - { - "depends_on": [ - "submodules" - ], - "image": "rancher/drone-golangci-lint:latest", - "name": "lint" - }, - { - "commands": [ - "go test -v -race ./... -cover" - ], - "depends_on": [ - "submodules" - ], - "image": "golang:1.18", - "name": "test" - } - ], - "trigger": { - "event": { - "exclude": [ - "tag" - ] - } - }, - "type": "docker" -} ---- -{ - "kind": "pipeline", - "name": "release", - "steps": [ - { - "commands": [ - "git fetch --tags", - "git submodule update --init --recursive" - ], - "image": "alpine/git", - "name": "fetch" - }, - { - "commands": [ - "go test -race ./... -cover" - ], - "image": "golang", - "name": "test" - }, - { - "commands": [ - "goreleaser release" - ], - "environment": { - "GITEA_TOKEN": { - "from_secret": "GITEA_TOKEN" - } - }, - "image": "goreleaser/goreleaser", - "name": "release" - } - ], - "trigger": { - "event": [ - "tag" - ] - }, - "type": "docker" -} diff --git a/cli/cli.go b/cli/cli.go index d034374..4b09779 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -4,7 +4,6 @@ package cli import ( "fmt" - "net" "os" "runtime" "strconv" @@ -13,12 +12,12 @@ import ( "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) @@ -89,7 +88,7 @@ func ParseCLI(version string) (Options, error) { 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", 1, "sets verbosity `level`", flag.OptShorthand('v'), flag.OptNoOptDefVal("2")) @@ -102,13 +101,13 @@ 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)}, err + return Options{Logger: util.InitLogger(*verbosity)}, fmt.Errorf("EDNS MBZ error: %w", err) } opts := Options{ @@ -170,33 +169,9 @@ func ParseCLI(version string) (Options, error) { // TODO: DRY if *subnet != "" { - ip, inet, err := net.ParseCIDR(*subnet) + err := parseSubnet(*subnet, &opts) 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), - } - } else { - return Options{Logger: util.InitLogger(*verbosity)}, err - } - } else { - sub, _ := inet.Mask.Size() - opts.EDNS.Subnet = *new(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 opts, err } } diff --git a/cli/cli_test.go b/cli/cli_test.go index 93a5a4f..edeb947 100644 --- a/cli/cli_test.go +++ b/cli/cli_test.go @@ -11,6 +11,7 @@ import ( "gotest.tools/v3/assert" ) +// nolint: paralleltest func TestEmpty(t *testing.T) { old := os.Args os.Args = []string{"awl", "-4"} @@ -21,6 +22,7 @@ func TestEmpty(t *testing.T) { os.Args = old } +// nolint: paralleltest func TestTLSPort(t *testing.T) { old := os.Args os.Args = []string{"awl", "-T"} @@ -30,6 +32,7 @@ func TestTLSPort(t *testing.T) { os.Args = old } +// nolint: paralleltest func TestSubnet(t *testing.T) { old := os.Args os.Args = []string{"awl", "--subnet", "127.0.0.1/32"} @@ -50,6 +53,7 @@ func TestSubnet(t *testing.T) { os.Args = old } +// nolint: paralleltest func TestInvalidFlag(t *testing.T) { old := os.Args os.Args = []string{"awl", "--treebug"} @@ -58,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, "digflags: 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"} @@ -74,6 +80,7 @@ func TestVersion(t *testing.T) { os.Args = old } +// nolint: paralleltest func TestTimeout(t *testing.T) { args := [][]string{ {"awl", "+timeout=0"}, @@ -89,6 +96,7 @@ func TestTimeout(t *testing.T) { } } +// nolint: paralleltest func TestRetries(t *testing.T) { args := [][]string{ {"awl", "+retry=-2"}, @@ -105,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 { diff --git a/cli/dig.go b/cli/dig.go index 8a09220..3f48022 100644 --- a/cli/dig.go +++ b/cli/dig.go @@ -4,15 +4,15 @@ package cli import ( "fmt" - "net" "strconv" "strings" "time" - - "github.com/miekg/dns" ) -// 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") @@ -121,11 +121,9 @@ func ParseDig(arg string, opts *Options) error { // Recursive switch statements WOO arg := strings.Split(arg, "=") switch arg[0] { - case "time", "timeout": if len(arg) > 1 && arg[1] != "" { timeout, err := strconv.Atoi(arg[1]) - if err != nil { return fmt.Errorf("digflags: Invalid timeout value: %w", err) } @@ -188,40 +186,16 @@ func ParseDig(arg string, opts *Options) error { case "subnet": if len(arg) > 1 && arg[1] != "" { - ip, inet, err := net.ParseCIDR(arg[1]) + err := parseSubnet(arg[1], opts) if err != nil { - // TODO: make not a default? - if arg[1] == "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 - } else { - return fmt.Errorf("digflags: Invalid EDNS Subnet: %w", errNoArg) - } - } - subnet, _ := inet.Mask.Size() - opts.EDNS.Subnet = *new(dns.EDNS0_SUBNET) - opts.EDNS.Subnet.Address = ip - opts.EDNS.Subnet.SourceNetmask = uint8(subnet) - 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 fmt.Errorf("digflags: Invalid EDNS Subnet: %w", err) } } else { return fmt.Errorf("digflags: Invalid EDNS Subnet: %w", errNoArg) } default: - return fmt.Errorf("digflags: unknown flag %s given", arg) + return &errInvalidArg{arg[0]} } } return nil diff --git a/cli/misc.go b/cli/misc.go index 6d8e1b7..282d52d 100644 --- a/cli/misc.go +++ b/cli/misc.go @@ -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 @@ -60,7 +60,7 @@ func ParseMiscArgs(args []string, opts *Options) error { opts.Logger.Info(arg, "detected as a domain name") opts.Request.Name, err = idna.ToASCII(arg) if err != nil { - return err + return fmt.Errorf("punycode translate error: %w", err) } // DNS query type @@ -73,7 +73,7 @@ func ParseMiscArgs(args []string, opts *Options) error { 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) } } } @@ -113,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 } } } @@ -143,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) } } diff --git a/cli/misc_test.go b/cli/misc_test.go index 929c1b4..80afee5 100644 --- a/cli/misc_test.go +++ b/cli/misc_test.go @@ -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) - } }) } diff --git a/cli/options.go b/cli/options.go index 9183e38..9508093 100644 --- a/cli/options.go +++ b/cli/options.go @@ -4,10 +4,11 @@ package cli import ( "errors" + "fmt" + "net" "git.froth.zone/sam/awl/internal/helpers" "git.froth.zone/sam/awl/logawl" - "github.com/miekg/dns" ) @@ -35,7 +36,7 @@ type Options struct { Reverse bool // Make reverse query Verbosity int // Set logawl verbosity HumanTTL bool // Make TTL human readable - ShowTTL bool //Display TTL + ShowTTL bool // Display TTL Short bool // Short output Identify bool // If short, add identity stuffs JSON bool // Outout as JSON @@ -47,7 +48,7 @@ type Options struct { EDNS // EDNS } -// What to (and not to) display +// What to (and not to) display. type Displays struct { Comments bool Question bool // QUESTION SECTION @@ -73,6 +74,48 @@ type EDNS struct { 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) +} diff --git a/conf/plan9.go b/conf/plan9.go index 2819c92..9da5690 100644 --- a/conf/plan9.go +++ b/conf/plan9.go @@ -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") diff --git a/conf/plan9_test.go b/conf/plan9_test.go index af8379a..a5123fc 100644 --- a/conf/plan9_test.go +++ b/conf/plan9_test.go @@ -6,7 +6,6 @@ import ( "testing" "git.froth.zone/sam/awl/conf" - "gotest.tools/v3/assert" ) diff --git a/conf/unix.go b/conf/unix.go index 12eb49f..9809b48 100644 --- a/conf/unix.go +++ b/conf/unix.go @@ -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 { diff --git a/conf/win.go b/conf/win.go index 2e5d23c..6b5712e 100644 --- a/conf/win.go +++ b/conf/win.go @@ -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 { diff --git a/doc/awl.1 b/doc/awl.1 index 27e4a92..1da461e 100644 --- a/doc/awl.1 +++ b/doc/awl.1 @@ -5,7 +5,7 @@ .nh .ad l .\" Begin generated content: -.TH "awl" "1" "2022-08-01" +.TH "awl" "1" "2022-08-03" .PP .SH NAME awl - DNS lookup tool @@ -23,11 +23,11 @@ where .PP .SH DESCRIPTION .PP -\fBawl\fR (\fBa\fRwls \fBw\fRant \fBl\fRicorice) is a simple tool designed to make DNS queries -, much like the venerable \fIdig\fR(1).\& An awl is a tool used to make small holes, +\fBawl\fR (\fBa\fRwls \fBw\fRant \fBl\fRicorice) is a simple tool designed to make DNS queries, +much like the venerable \fIdig\fR(1).\& An awl is a tool used to make small holes, typically used in leatherworking.\& .PP -\fBawl\fR is designed to be a more "modern" version of \fIdrill\fR(1) by including +\fBawl\fR is designed to be a more "modern" version of \fIdrill\fR(1) by including some more recent RFCs and output options.\& \fBawl\fR is still heavily Work-In-Progress so some features may get added or removed.\& .PP @@ -39,10 +39,6 @@ Dig-like +[no]flags are supported, see dig(1) .br Enable DNSSEC.\& This needs to be manually enabled.\& .PP -\fB--qr\fR, \fB+qr\fR -.br - Print query before sending.\& -.PP \fB-v\fR \fIvalue\fR .br Set verbosity (currently WIP) @@ -132,7 +128,7 @@ Dig-like +[no]flags are supported, see dig(1) .br 0.\&5 seconds is the minimum.\& .PP -\fB--retries\fR \fIint\fR, \fB+tries\fR=\fIint\fR, \fB+retry\fR=\fIint\fR +\fB--retries\fR \fIint\fR, \fB+tries\fR=\fIint\fR, \fB+ retry\fR=\fIint\fR .br Set the number of retries.\& .br @@ -142,109 +138,61 @@ Dig-like +[no]flags are supported, see dig(1) .SS DNS Flags .PP .RS 4 -\fB--aa\fR[\fI=false\fR], \fB+[no]aaflag\fR +\fB--aa=[false]\fR, \fB+[no]aaflag\fR .br (Set, Unset) AA (Authoritative Answer) flag .PP -\fB--ad\fR[\fI=false\fR], \fB+[no]adflag\fR +\fB--ad=[false]\fR, \fB+[no]adflag\fR .br (Set, Unset) AD (Authenticated Data) flag .PP -\fB--tc\fR[\fI=false\fR], \fB+[no]tcflag\fR +\fB--tc=[false]\fR, \fB+[no]tcflag\fR .br (Set, Unset) TC (TrunCated) flag .PP -\fB-z\fR[\fI=false\fR], \fB+[no]zflag\fR +\fB-z=[false]\fR, \fB+[no]zflag\fR .br (Set, Unset) Z (Zero) flag .PP -\fB--cd\fR[\fI=false\fR], \fB+[no]cdflag\fR +\fB--cd=[false]\fR, \fB+[no]cdflag\fR .br (Set, Unset) CD (Checking Disabled) flag .PP -\fB--qr\fR[\fI=false\fR], \fB+[no]qrflag\fR +\fB--qr=[false]\fR, \fB+[no]qrflag\fR .br (Set, Unset) QR (QueRy) flag .PP -\fB--rd\fR[\fI=true\fR], \fB+[no]rdflag\fR +\fB--rd=[true]\fR, \fB+[no]rdflag\fR .br (Set, Unset) RD (Recursion Desired) flag .PP -\fB--ra\fR[\fI=false\fR], \fB+[no]raflag\fR +\fB--ra=[false]\fR, \fB+[no]raflag\fR .br (Set, Unset) RA (Recursion Available) flag .PP .RE -.SS EDNS Flags -.RS 4 -\fB--edns-ver\fR \fIver\fR, \fB+edns\fR[\fI=ver\fR] -.br - Set EDNS version to \fIver\fR (default 0) -.PP -\fB--no-edns\fR, \fB+noedns\fR -.br - \fB--no-edns\fR and \fB+noedns\fR disable EDNS entirely -.PP -\fB--expire\fR, \fB+[no]expire\fR -.br - Add EDNS expire option to query -.PP -\fB--nsid\fR, \fB+[no]nsid\fR -.br - Add EDNS NSID option to query -.PP -\fB--no-cookie\fR, \fB+[no]cookie\fR -.br - Send an EDNS cookie (sent by default) -.PP -\fB--keep-alive\fR, \fB+[no]keepalive\fR, \fB+[no]keepopen\fR -.br - Send an EDNS TCP keepalive options -.PP -\fB--buffer-size\fR, \fB+bufsize\fR=\fIint\fR -.br - Set UDP buffer size to int (default 1232) -.PP -\fB--zflags\fR \fIflag\fR, \fB+ednsflags\fR[\fI=flag\fR] -.br - Set Z EDNS flag bits.\& Octal, decimal and hex are accepted.\& -Setting the first bit (DO) will be ignored, use \fB-D\fR instead -.PP -\fB--pad\fR, \fB+padding\fR -.br - Set EDNS padding -.PP -\fB+subnet\fR=\fIaddr/prefix\fR -.br - Set EDNS client subnet -.PP -.RE .SS Output Display .RS 4 -\fB--no-question\fR, \fB+[no]question\fR +\fB--no-question\fR, \fB+noquestion\fR .br Do not display the Question section .PP -\fB--no-answer\fR, \fB+[no]answer\fR +\fB--no-answer\fR, \fB+noanswer\fR .br Do not display the Answer section .PP -\fB--no-answer\fR, \fB+[no]answer\fR +\fB--no-answer\fR, \fB+noanswer\fR .br Do not display the Answer section .PP -\fB--no-authority\fR, \fB+[no]authority\fR +\fB--no-authority\fR, \fB+noauthority\fR .br Do not display the Authority section .PP -\fB--no-additional\fR, \fB+[no]additional\fR +\fB--no-additional\fR, \fB+noadditional\fR .br Do not display the Additional section .PP -\fB--no-comments\fR, \fB+[no]comments\fR -.br - Do not display the query comments -.PP \fB--no-statistics\fR, \fB+nostats\fR .br Do not display the Statistics (additional comments) section @@ -293,10 +241,4 @@ awl -xT PTR 8\&.8\&.4\&.4 @dns\&.google Query dns.\&google over TLS for the PTR record to the IP address 8.\&8.\&4.\&4 .PP .SH SEE ALSO -\fIdrill\fR(1), \fIdig\fR(1), the many DNS RFCs -.PP -.SH BUGS -Numerous, report them either to -.br -https://git.\&froth.\&zone/sam/awl/issues or via email -~sammefishe/awl-dev@lists.\&sr.\&ht \ No newline at end of file +\fIdrill\fR(1), \fIdig\fR(1), the many DNS RFCs \ No newline at end of file diff --git a/docs.go b/docs.go index 3ba701c..f868989 100644 --- a/docs.go +++ b/docs.go @@ -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 diff --git a/go.mod b/go.mod index d6bfd76..cd6db6b 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ 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.12 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect diff --git a/go.sum b/go.sum index 5413b84..ef6e1d5 100644 --- a/go.sum +++ b/go.sum @@ -219,8 +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-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= @@ -263,8 +261,6 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/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= diff --git a/logawl/logawl.go b/logawl/logawl.go index 70c9416..52701b6 100644 --- a/logawl/logawl.go +++ b/logawl/logawl.go @@ -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") diff --git a/logawl/logger.go b/logawl/logger.go index b703ba2..aaf29a7 100644 --- a/logawl/logger.go +++ b/logawl/logger.go @@ -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 YYYY/MM/DD HH:MM:SS (local time) +// Formats the log header as such YYYY/MM/DD HH:MM:SS (local time) . 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...) } diff --git a/logawl/logging_test.go b/logawl/logging_test.go index ffbcf60..e30eef9 100644 --- a/logawl/logging_test.go +++ b/logawl/logging_test.go @@ -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") } diff --git a/main_test.go b/main_test.go index 46aa256..6091ce8 100644 --- a/main_test.go +++ b/main_test.go @@ -5,9 +5,15 @@ 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) } diff --git a/query/DNSCrypt.go b/query/DNSCrypt.go index 12751f8..70e5151 100644 --- a/query/DNSCrypt.go +++ b/query/DNSCrypt.go @@ -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") diff --git a/query/DNSCrypt_test.go b/query/DNSCrypt_test.go index 126a073..d49bb30 100644 --- a/query/DNSCrypt_test.go +++ b/query/DNSCrypt_test.go @@ -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") + } + }) } - } diff --git a/query/HTTPS.go b/query/HTTPS.go index b15dd81..91c23f4 100644 --- a/query/HTTPS.go +++ b/query/HTTPS.go @@ -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) +} diff --git a/query/HTTPS_test.go b/query/HTTPS_test.go index b6efae6..ee552a0 100644 --- a/query/HTTPS_test.go +++ b/query/HTTPS_test.go @@ -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" ) diff --git a/query/QUIC.go b/query/QUIC.go index e9e5dd1..10bf474 100644 --- a/query/QUIC.go +++ b/query/QUIC.go @@ -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 } diff --git a/query/QUIC_test.go b/query/QUIC_test.go index b8837d1..a02d930 100644 --- a/query/QUIC_test.go +++ b/query/QUIC_test.go @@ -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" ) diff --git a/query/general.go b/query/general.go index 68a5f8d..eca10c1 100644 --- a/query/general.go +++ b/query/general.go @@ -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 } diff --git a/query/general_test.go b/query/general_test.go index 5b080b9..7346e63 100644 --- a/query/general_test.go +++ b/query/general_test.go @@ -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{}) + }) } - } diff --git a/query/print.go b/query/print.go index 337ccd6..f5cf4eb 100644 --- a/query/print.go +++ b/query/print.go @@ -5,19 +5,18 @@ package query import ( "encoding/json" "encoding/xml" + "errors" "fmt" "strconv" "strings" "time" "git.froth.zone/sam/awl/cli" - "golang.org/x/net/idna" - "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 { @@ -40,10 +39,12 @@ func PrintSpecial(msg *dns.Msg, opts cli.Options) (string, error) { return string(yaml), err default: - return "", fmt.Errorf("special: no recognized format given") + 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{ @@ -55,7 +56,7 @@ func MakePrintable(msg *dns.Msg, opts cli.Options) (*Message, error) { if opts.Display.UcodeTranslate { name, err = idna.ToUnicode(question.Name) if err != nil { - return nil, err + return nil, fmt.Errorf("punycode: error translating to unicode: %w", err) } } else { name = question.Name @@ -83,7 +84,7 @@ func MakePrintable(msg *dns.Msg, opts cli.Options) (*Message, error) { if opts.Display.UcodeTranslate { name, err = idna.ToUnicode(answer.Header().Name) if err != nil { - return nil, err + return nil, fmt.Errorf("punycode: error translating to unicode: %w", err) } } else { name = answer.Header().Name @@ -98,7 +99,6 @@ func MakePrintable(msg *dns.Msg, opts cli.Options) (*Message, error) { }, Value: temp[len(temp)-1], }) - } for _, ns := range msg.Ns { @@ -117,7 +117,7 @@ func MakePrintable(msg *dns.Msg, opts cli.Options) (*Message, error) { if opts.Display.UcodeTranslate { name, err = idna.ToUnicode(ns.Header().Name) if err != nil { - return nil, err + return nil, fmt.Errorf("punycode: error translating to unicode: %w", err) } } else { name = ns.Header().Name @@ -153,7 +153,7 @@ func MakePrintable(msg *dns.Msg, opts cli.Options) (*Message, error) { if opts.Display.UcodeTranslate { name, err = idna.ToUnicode(additional.Header().Name) if err != nil { - return nil, err + return nil, fmt.Errorf("punycode: error translating to unicode: %w", err) } } else { name = additional.Header().Name @@ -173,3 +173,5 @@ func MakePrintable(msg *dns.Msg, opts cli.Options) (*Message, error) { return &ret, nil } + +var errInvalidFormat = errors.New("this should never happen") diff --git a/query/print_test.go b/query/print_test.go new file mode 100644 index 0000000..732cb69 --- /dev/null +++ b/query/print_test.go @@ -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)) == " MsgHdr") +} diff --git a/query/query.go b/query/query.go index 05e2c93..0373e56 100644 --- a/query/query.go +++ b/query/query.go @@ -8,7 +8,6 @@ import ( "git.froth.zone/sam/awl/cli" "git.froth.zone/sam/awl/internal/helpers" - "github.com/dchest/uniuri" "github.com/miekg/dns" ) @@ -30,7 +29,6 @@ func CreateQuery(opts cli.Options) (helpers.Response, error) { // EDNS time :) if opts.EDNS.EnableEDNS { - o := new(dns.OPT) o.Hdr.Name = "." o.Hdr.Rrtype = dns.TypeOPT @@ -81,12 +79,10 @@ func CreateQuery(opts cli.Options) (helpers.Response, error) { } 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") - } + } 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) @@ -112,7 +108,7 @@ func CreateQuery(opts cli.Options) (helpers.Response, error) { opts.Display.Statistics = temp str += "\n;; QUERY SIZE: " + strconv.Itoa(req.Len()) } - fmt.Println(str, "\n------") + fmt.Println(str) opts.ShowQuery = false } } @@ -123,5 +119,6 @@ func CreateQuery(opts cli.Options) (helpers.Response, error) { } opts.Logger.Info("Query successfully loaded") + //nolint:wrapcheck // Error wrapping not needed here return resolver.LookUp(req) } diff --git a/query/query_test.go b/query/query_test.go index d06c58a..6b6ee66 100644 --- a/query/query_test.go +++ b/query/query_test.go @@ -9,100 +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, + 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.", + 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, + }, }, - 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{}) -} - -func TestCreateQr(t *testing.T) { - opts := cli.Options{ - Logger: util.InitLogger(0), - Port: 53, - QR: false, - Z: true, - RD: false, - ShowQuery: true, - - Request: helpers.Request{ - Server: "8.8.4.4", - Type: dns.TypeA, - Name: "example.com.", - }, - EDNS: cli.EDNS{ - EnableEDNS: false, - DNSSEC: true, - Cookie: false, - Expire: false, - KeepOpen: false, - Nsid: false, - }, - Display: cli.Displays{ - Comments: true, - Question: true, - Answer: true, - Authority: true, - Additional: true, - Statistics: true, - UcodeTranslate: true, - }, + for _, opt := range in { + opt := opt + t.Run("", func(t *testing.T) { + t.Parallel() + res, err := query.CreateQuery(opt) + assert.NilError(t, err) + assert.Assert(t, res != helpers.Response{}) + str, err := query.PrintSpecial(res.DNS, opt) + assert.NilError(t, err) + assert.Assert(t, str != "") + str = query.ToString(res, opt) + assert.Assert(t, str != "") + }) } - res, err := query.CreateQuery(opts) - assert.NilError(t, err) - assert.Assert(t, res != helpers.Response{}) -} - -func TestCreateQr2(t *testing.T) { - opts := cli.Options{ - 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.", - }, - 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{}) } diff --git a/query/resolver.go b/query/resolver.go index 7c035ee..51740e6 100644 --- a/query/resolver.go +++ b/query/resolver.go @@ -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: diff --git a/query/struct.go b/query/struct.go index f776331..b4388da 100644 --- a/query/struct.go +++ b/query/struct.go @@ -9,42 +9,44 @@ import ( "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:",omitempty" xml:",omitempty" yaml:",omitempty"` - Question []Question `json:",omitempty" xml:",omitempty" yaml:",omitempty"` - Answer []Answer `json:",omitempty" xml:",omitempty" yaml:",omitempty"` - Ns []Answer `json:",omitempty" xml:",omitempty" yaml:",omitempty"` - Extra []Answer `json:",omitempty" xml:",omitempty" yaml:",omitempty"` + 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:",omitempty" xml:",omitempty" yaml:",omitempty"` - Type string `json:",omitempty" xml:",omitempty" yaml:",omitempty"` - Class string `json:",omitempty" xml:",omitempty" yaml:",omitempty"` + 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:",omitempty" xml:",omitempty" yaml:",omitempty"` - Type string `json:",omitempty" xml:",omitempty" yaml:",omitempty"` - Class string `json:",omitempty" xml:",omitempty" yaml:",omitempty"` - TTL string `json:",omitempty" xml:",omitempty" yaml:",omitempty"` + 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"` + RRHeader `json:"header,omitempty" xml:"header,omitempty" yaml:"header,omitempty"` + Value string `json:"response,omitempty" xml:"response,omitempty" yaml:"response,omitempty"` } -// Turn the response into dig +// 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 " MsgHdr" } @@ -52,7 +54,6 @@ func ToString(res helpers.Response, opts cli.Options) 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)) + ", " @@ -134,14 +135,18 @@ func ToString(res helpers.Response, opts cli.Options) string { } } else { // Print just the responses, nothing else - for _, res := range res.DNS.Answer { - temp := strings.Split(res.String(), "\t") + 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" + } + } - if opts.Identify { - s += " from server " + opts.Request.Server + " in " + res.RTT.String() - } - // s += "\n" } return s diff --git a/template.mk b/template.mk index 57cfc31..93ffe84 100644 --- a/template.mk +++ b/template.mk @@ -24,15 +24,18 @@ doc/$(PROG).1: doc/wiki/$(PROG).1.md @cp doc/awl.1 doc/awl.bak $(SCDOC) 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 ./... -## cover: generates test coverage, output as HTML -cover: test +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: coverage/coverage.out + fmt: gofmt -w -s . @@ -41,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: diff --git a/util/logger.go b/util/logger.go index 037d4d7..1c4bb03 100644 --- a/util/logger.go +++ b/util/logger.go @@ -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 } diff --git a/util/logger_test.go b/util/logger_test.go index a9502be..0dce589 100644 --- a/util/logger_test.go +++ b/util/logger_test.go @@ -11,6 +11,7 @@ import ( ) func TestInitLogger(t *testing.T) { + t.Parallel() logger := util.InitLogger(0) assert.Equal(t, logger.Level, logawl.Level(0)) } diff --git a/util/reverseDNS.go b/util/reverseDNS.go index 57d800e..cfaef14 100644 --- a/util/reverseDNS.go +++ b/util/reverseDNS.go @@ -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. diff --git a/util/reverseDNS_test.go b/util/reverseDNS_test.go index 7841e40..6e2fae6 100644 --- a/util/reverseDNS_test.go +++ b/util/reverseDNS_test.go @@ -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") } -- 2.43.0