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