feat: more API work
continuous-integration/drone/push Build was killed Details

Reviewed-on: #94
Reviewed-by: grumbulon <grumbulon@grumbulon.xyz>
This commit is contained in:
Sam Therapy 2022-09-15 11:02:43 +00:00
parent 0d011bb097
commit 9746ae0a6f
24 changed files with 661 additions and 540 deletions

View File

@ -8,19 +8,9 @@ local testing(version, arch) = {
arch: arch
},
steps: [
{
name: "compile",
image: "golang:" + version,
commands: [
"make awl"
],
},
{
name: "lint",
image: "rancher/drone-golangci-lint:latest",
depends_on: [
"compile",
],
},
{
name: "test",
@ -74,7 +64,7 @@ local release() = {
name: "test",
image: "golang",
commands: [
"make test"
"make test-ci"
]
},
{

12
.editorconfig Normal file
View File

@ -0,0 +1,12 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = false

View File

@ -32,6 +32,7 @@ linters:
- predeclared
- revive
- staticcheck
- tagliatelle
- whitespace
- wrapcheck
- wsl
@ -70,6 +71,16 @@ linters-settings:
- name: unexported-return
- name: var-declaration
- name: var-naming
linters-settings:
tagliatelle:
case:
use-field-name: false
rules:
# Any struct tag type can be used.
# Support string case: `camel`, `pascal`, `kebab`, `snake`, `goCamel`, `goPascal`, `goKebab`, `goSnake`, `upper`, `lower`
json: goCamel
yaml: goCamel
xml: goCamel
issues:
exclude-use-default: false

View File

@ -5,7 +5,6 @@ package cli
import (
"errors"
"fmt"
"os"
"runtime"
"strconv"
"strings"
@ -18,8 +17,8 @@ import (
// ParseCLI parses arguments given from the CLI and passes them into an `Options`
// struct.
func ParseCLI(version string) (util.Options, error) {
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
func ParseCLI(args []string, version string) (util.Options, error) {
flag.CommandLine = flag.NewFlagSet(args[0], flag.ContinueOnError)
flag.Usage = func() {
fmt.Println(`awl - drill, writ small
@ -104,7 +103,7 @@ func ParseCLI(version string) (util.Options, error) {
flag.CommandLine.SortFlags = false
// Parse the flags
if err := flag.CommandLine.Parse(os.Args[1:]); err != nil {
if err := flag.CommandLine.Parse(args[1:]); err != nil {
return util.Options{Logger: util.InitLogger(*verbosity)}, fmt.Errorf("flag: %w", err)
}
@ -116,7 +115,6 @@ func ParseCLI(version string) (util.Options, error) {
opts := util.Options{
Logger: util.InitLogger(*verbosity),
Port: *port,
IPv4: *ipv4,
IPv6: *ipv6,
Short: *short,
@ -128,29 +126,29 @@ func ParseCLI(version string) (util.Options, error) {
HTTPS: *https,
QUIC: *quic,
Truncate: *truncate,
ShowQuery: false,
AA: *aaflag,
AD: *adflag,
TC: *tcflag,
Z: *zflag,
CD: *cdflag,
QR: *qrflag,
RD: *rdflag,
RA: *raflag,
Reverse: *reverse,
HumanTTL: false,
ShowTTL: true,
JSON: *json,
XML: *xml,
YAML: *yaml,
HeaderFlags: util.HeaderFlags{
AA: *aaflag,
AD: *adflag,
TC: *tcflag,
Z: *zflag,
CD: *cdflag,
QR: *qrflag,
RD: *rdflag,
RA: *raflag,
},
Request: util.Request{
Type: dns.StringToType[strings.ToUpper(*qType)],
Class: dns.StringToClass[strings.ToUpper(*class)],
Name: *query,
Timeout: time.Duration(*timeout * float32(time.Second)),
Retries: *retry,
Port: *port,
},
Display: util.Displays{
Display: util.Display{
Comments: !*noC,
Question: !*noQ,
Opt: !*noOpt,
@ -158,6 +156,9 @@ func ParseCLI(version string) (util.Options, error) {
Authority: !*noAuth,
Additional: !*noAdd,
Statistics: !*noStats,
HumanTTL: false,
ShowQuery: false,
TTL: true,
},
EDNS: util.EDNS{
EnableEDNS: !*edns,
@ -199,15 +200,15 @@ func ParseCLI(version string) (util.Options, error) {
opts.Logger.Info("Dig/Drill flags parsed")
opts.Logger.Debug(fmt.Sprintf("%+v", opts))
if opts.Port == 0 {
if opts.Request.Port == 0 {
if opts.TLS || opts.QUIC {
opts.Port = 853
opts.Request.Port = 853
} else {
opts.Port = 53
opts.Request.Port = 53
}
}
opts.Logger.Info("Port set to", opts.Port)
opts.Logger.Info("Port set to", opts.Request.Port)
// Set timeout to 0.5 seconds if set below 0.5
if opts.Request.Timeout < (time.Second / 2) {
@ -218,6 +219,9 @@ func ParseCLI(version string) (util.Options, error) {
opts.Request.Retries = 0
}
opts.Logger.Info("Options fully populated")
opts.Logger.Debug(fmt.Sprintf("%+v", opts))
return opts, nil
}

View File

@ -3,7 +3,6 @@
package cli_test
import (
"os"
"testing"
"time"
@ -12,156 +11,110 @@ import (
)
func TestEmpty(t *testing.T) {
args := os.Args
os.Args = []string{"awl", "-4"}
args := []string{"awl", "-4"}
opts, err := cli.ParseCLI("TEST")
opts, err := cli.ParseCLI(args, "TEST")
assert.NilError(t, err)
assert.Equal(t, opts.Port, 53)
assert.Equal(t, opts.Request.Port, 53)
assert.Assert(t, opts.IPv4)
os.Args = args
}
func TestTLSPort(t *testing.T) {
args := os.Args
os.Args = []string{"awl", "-T"}
args := []string{"awl", "-T"}
opts, err := cli.ParseCLI("TEST")
opts, err := cli.ParseCLI(args, "TEST")
assert.NilError(t, err)
assert.Equal(t, opts.Port, 853)
os.Args = args
assert.Equal(t, opts.Request.Port, 853)
}
func TestSubnet(t *testing.T) {
args := os.Args
os.Args = []string{"awl", "--subnet", "127.0.0.1/32"}
args := []string{"awl", "--subnet", "127.0.0.1/32"}
opts, err := cli.ParseCLI("TEST")
opts, err := cli.ParseCLI(args, "TEST")
assert.NilError(t, err)
assert.Equal(t, opts.EDNS.Subnet.Family, uint16(1))
os.Args = args
args = []string{"awl", "--subnet", "0"}
os.Args = []string{"awl", "--subnet", "0"}
opts, err = cli.ParseCLI("TEST")
opts, err = cli.ParseCLI(args, "TEST")
assert.NilError(t, err)
assert.Equal(t, opts.EDNS.Subnet.Family, uint16(1))
os.Args = args
args = []string{"awl", "--subnet", "::/0"}
os.Args = []string{"awl", "--subnet", "::/0"}
opts, err = cli.ParseCLI("TEST")
opts, err = cli.ParseCLI(args, "TEST")
assert.NilError(t, err)
assert.Equal(t, opts.EDNS.Subnet.Family, uint16(2))
os.Args = args
args = []string{"awl", "--subnet", "/"}
os.Args = []string{"awl", "--subnet", "/"}
opts, err = cli.ParseCLI("TEST")
opts, err = cli.ParseCLI(args, "TEST")
assert.ErrorContains(t, err, "EDNS subnet")
os.Args = args
}
func TestMBZ(t *testing.T) { //nolint: paralleltest // Race conditions
args := os.Args
os.Args = []string{"awl", "--zflag", "G"}
func TestMBZ(t *testing.T) {
args := []string{"awl", "--zflag", "G"}
_, err := cli.ParseCLI("TEST")
_, err := cli.ParseCLI(args, "TEST")
assert.ErrorContains(t, err, "EDNS MBZ")
os.Args = args
}
func TestInvalidFlag(t *testing.T) { //nolint: paralleltest // Race conditions
args := os.Args
stdout := os.Stdout
stderr := os.Stderr
func TestInvalidFlag(t *testing.T) {
args := []string{"awl", "--treebug"}
os.Stdout = os.NewFile(0, os.DevNull)
os.Stderr = os.NewFile(0, os.DevNull)
os.Args = []string{"awl", "--treebug"}
_, err := cli.ParseCLI("TEST")
_, err := cli.ParseCLI(args, "TEST")
assert.ErrorContains(t, err, "unknown flag")
os.Args = args
os.Stdout = stdout
os.Stderr = stderr
}
func TestInvalidDig(t *testing.T) { //nolint: paralleltest // Race conditions
args := os.Args
os.Args = []string{"awl", "+a"}
func TestInvalidDig(t *testing.T) {
args := []string{"awl", "+a"}
_, err := cli.ParseCLI("TEST")
_, err := cli.ParseCLI(args, "TEST")
assert.ErrorContains(t, err, "digflags: invalid argument")
os.Args = args
}
func TestVersion(t *testing.T) { //nolint: paralleltest // Race conditions
args := os.Args
stdout := os.Stdout
stderr := os.Stderr
func TestVersion(t *testing.T) {
args := []string{"awl", "--version"}
os.Args = []string{"awl", "--version"}
_, err := cli.ParseCLI("test")
_, err := cli.ParseCLI(args, "test")
assert.ErrorType(t, err, cli.ErrNotError)
os.Args = args
os.Stdout = stdout
os.Stderr = stderr
}
func TestTimeout(t *testing.T) { //nolint: paralleltest // Race conditions
func TestTimeout(t *testing.T) {
args := [][]string{
{"awl", "+timeout=0"},
{"awl", "--timeout", "0"},
}
for _, test := range args {
args := os.Args
os.Args = test
test := test
opt, err := cli.ParseCLI("TEST")
opt, err := cli.ParseCLI(test, "TEST")
assert.NilError(t, err)
assert.Equal(t, opt.Request.Timeout, time.Second/2)
os.Args = args
}
}
func TestRetries(t *testing.T) { //nolint: paralleltest // Race conditions
func TestRetries(t *testing.T) {
args := [][]string{
{"awl", "+retry=-2"},
{"awl", "+tries=-2"},
{"awl", "--retries", "-2"},
}
for _, test := range args {
args := os.Args
os.Args = test
test := test
opt, err := cli.ParseCLI("TEST")
opt, err := cli.ParseCLI(test, "TEST")
assert.NilError(t, err)
assert.Equal(t, opt.Request.Retries, 0)
os.Args = args
}
}
@ -175,10 +128,8 @@ func FuzzFlags(f *testing.F) {
f.Fuzz(func(t *testing.T, orig string) {
// Get rid of outputs
args := os.Args
os.Args = []string{"awl", orig}
args := []string{"awl", orig}
//nolint:errcheck,gosec // Only make sure the program does not crash
cli.ParseCLI("TEST")
os.Args = args
cli.ParseCLI(args, "TEST")
})
}

View File

@ -45,13 +45,13 @@ func ParseDig(arg string, opts *util.Options) error {
// End DNS query flags
case "qr":
opts.ShowQuery = isNo
opts.Display.ShowQuery = isNo
case "ttlunits":
opts.HumanTTL = isNo
opts.Display.HumanTTL = isNo
case "ttl", "ttlid":
opts.ShowTTL = isNo
opts.Display.TTL = isNo
case "class":
opts.ShowClass = isNo
opts.Display.ShowClass = isNo
// EDNS queries
case "dnssec":

13
go.mod
View File

@ -9,6 +9,7 @@ require (
github.com/miekg/dns v1.1.50
github.com/stefansundin/go-zflag v1.1.1
golang.org/x/net v0.0.0-20220909164309-bea034e7d591
golang.org/x/sys v0.0.0-20220913175220-63ea55921009
gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.3.0
)
@ -18,22 +19,18 @@ require (
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
github.com/ameshkov/dnsstamps v1.0.3 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 // indirect
)
require (
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect
golang.org/x/exp v0.0.0-20220914170420-dc92f8653013 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/sys v0.0.0-20220913175220-63ea55921009
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

14
go.sum
View File

@ -79,10 +79,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/exp v0.0.0-20220914170420-dc92f8653013 h1:ZjglnWxEUdPyXl4o/j4T89SRCI+4X6NW6185PNLEOF4=
golang.org/x/exp v0.0.0-20220914170420-dc92f8653013/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
@ -94,8 +92,6 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220907135653-1e95f45603a7 h1:1WGATo9HAhkWMbfyuVU0tEFP88OIkUvwaHFveQPvzCQ=
golang.org/x/net v0.0.0-20220907135653-1e95f45603a7/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -119,12 +115,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220907062415-87db552b00fd h1:AZeIEzg+8RCELJYq8w+ODLVxFgLMMigSwO/ffKPEd9U=
golang.org/x/sys v0.0.0-20220907062415-87db552b00fd/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2 h1:wM1k/lXfpc5HdkJJyW9GELpd8ERGdnh8sMGL6Gzq3Ho=
golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw=
golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

View File

@ -139,17 +139,37 @@ func (l *Logger) Debug(v ...any) {
l.Println(DebugLevel, v...)
}
// Debugf calls print after formatting the string with Debug level.
func (l *Logger) Debugf(format string, v ...any) {
l.Println(ErrLevel, fmt.Sprintf(format, v...))
}
// Info calls print directly with Info level.
func (l *Logger) Info(v ...any) {
l.Println(InfoLevel, v...)
}
// Infof calls print after formatting the string with Info level.
func (l *Logger) Infof(format string, v ...any) {
l.Println(ErrLevel, fmt.Sprintf(format, v...))
}
// Warn calls print directly with Warn level.
func (l *Logger) Warn(v ...any) {
l.Println(WarnLevel, v...)
}
// Warnf calls print after formatting the string with Warn level.
func (l *Logger) Warnf(format string, v ...any) {
l.Println(WarnLevel, fmt.Sprintf(format, v...))
}
// Error calls print directly with Error level.
func (l *Logger) Error(v ...any) {
l.Println(ErrLevel, v...)
}
// Errorf calls print after formatting the string with Error level.
func (l *Logger) Errorf(format string, v ...any) {
l.Println(ErrLevel, fmt.Sprintf(format, v...))
}

View File

@ -52,6 +52,7 @@ func TestLogger(t *testing.T) {
case 0:
fn := func() {
logger.Error("Test", "E")
logger.Errorf("%s", "Test")
}
var buffer bytes.Buffer
@ -62,6 +63,7 @@ func TestLogger(t *testing.T) {
case 1:
fn := func() {
logger.Warn("Test")
logger.Warnf("%s", "Test")
}
var buffer bytes.Buffer
@ -72,6 +74,7 @@ func TestLogger(t *testing.T) {
case 2:
fn := func() {
logger.Info("Test")
logger.Infof("%s", "Test")
}
var buffer bytes.Buffer
@ -83,6 +86,8 @@ func TestLogger(t *testing.T) {
fn := func() {
logger.Debug("Test")
logger.Debug("Test 2")
logger.Debugf("%s", "Test")
logger.Debugf("%s %d", "Test", 2)
}
var buffer bytes.Buffer

View File

@ -17,7 +17,7 @@ import (
var version = "DEV"
func main() {
if opts, code, err := run(); err != nil {
if opts, code, err := run(os.Args); err != nil {
// TODO: Make not ew
if errors.Is(err, cli.ErrNotError) || strings.Contains(err.Error(), "help requested") {
os.Exit(0)
@ -28,8 +28,8 @@ func main() {
}
}
func run() (opts util.Options, code int, err error) {
opts, err = cli.ParseCLI(version)
func run(args []string) (opts util.Options, code int, err error) {
opts, err = cli.ParseCLI(args, version)
if err != nil {
return opts, 1, fmt.Errorf("parse: %w", err)
}
@ -53,7 +53,7 @@ func run() (opts util.Options, code int, err error) {
var str string
if opts.JSON || opts.XML || opts.YAML {
str, err = query.PrintSpecial(resp.DNS, opts)
str, err = query.PrintSpecial(resp, opts)
if err != nil {
return opts, 10, fmt.Errorf("format print: %w", err)
}

View File

@ -14,33 +14,23 @@ func TestMain(t *testing.T) { //nolint: paralleltest // Race conditions
os.Stdout = os.NewFile(0, os.DevNull)
os.Stderr = os.NewFile(0, os.DevNull)
old := os.Args
args := []string{"awl", "+yaml", "@1.1.1.1"}
os.Args = []string{"awl", "+yaml", "@1.1.1.1"}
_, code, err := run()
_, code, err := run(args)
assert.NilError(t, err)
assert.Equal(t, code, 0)
os.Args = []string{"awl", "+short", "@1.1.1.1"}
args = []string{"awl", "+short", "@1.1.1.1"}
_, code, err = run()
_, code, err = run(args)
assert.NilError(t, err)
assert.Equal(t, code, 0)
os.Args = old
}
func TestHelp(t *testing.T) {
old := os.Args
os.Stdout = os.NewFile(0, os.DevNull)
os.Stderr = os.NewFile(0, os.DevNull)
args := []string{"awl", "-h"}
os.Args = []string{"awl", "-h"}
_, code, err := run()
_, code, err := run(args)
assert.ErrorIs(t, err, zflag.ErrHelp)
assert.Equal(t, code, 1)
os.Args = old
}

@ -1 +1 @@
Subproject commit c8686b1e149b6c42db019c77caf36475aafadd03
Subproject commit d876d6de34a78298ed041f575662015fb7eccdb5

View File

@ -22,8 +22,7 @@ func TestQuic(t *testing.T) {
opts := util.Options{
QUIC: true,
Logger: util.InitLogger(0),
Port: 853,
Request: util.Request{Server: "dns.adguard.com"},
Request: util.Request{Server: "dns.adguard.com", Port: 853},
}
testCase := util.Request{Server: "dns.//./,,adguard.com", Type: dns.TypeA, Name: "git.froth.zone"}
testCase2 := util.Request{Server: "dns.adguard.com", Type: dns.TypeA, Name: "git.froth.zone"}
@ -54,7 +53,7 @@ func TestQuic(t *testing.T) {
resolver, err := query.LoadResolver(opts)
assert.NilError(t, err)
testCase2.Server = net.JoinHostPort(testCase2.Server, strconv.Itoa(opts.Port))
testCase2.Server = net.JoinHostPort(testCase2.Server, strconv.Itoa(opts.Request.Port))
// if the domain is not canonical, make it canonical
if !strings.HasSuffix(testCase2.Name, ".") {
@ -78,8 +77,7 @@ func TestInvalidQuic(t *testing.T) {
opts := util.Options{
QUIC: true,
Logger: util.InitLogger(0),
Port: 853,
Request: util.Request{Server: "example.com", Type: dns.TypeA, Name: "git.froth.zone", Timeout: 10 * time.Millisecond},
Request: util.Request{Server: "example.com", Port: 853, Type: dns.TypeA, Name: "git.froth.zone", Timeout: 10 * time.Millisecond},
}
resolver, err := query.LoadResolver(opts)
assert.NilError(t, err)

View File

@ -17,9 +17,9 @@ func TestResolve(t *testing.T) {
opts := util.Options{
Logger: util.InitLogger(0),
Port: 53,
Request: util.Request{
Server: "8.8.4.1",
Port: 53,
Type: dns.TypeA,
Name: "example.com.",
Timeout: time.Second / 2,
@ -42,9 +42,9 @@ func TestTruncate(t *testing.T) {
opts := util.Options{
Logger: util.InitLogger(0),
IPv4: true,
Port: 5301,
Request: util.Request{
Server: "madns.binarystar.systems",
Port: 5301,
Type: dns.TypeTXT,
Name: "limit.txt.example.",
},
@ -70,9 +70,10 @@ func TestResolveAgain(t *testing.T) {
util.Options{
Logger: util.InitLogger(0),
TCP: true,
Port: 53,
Request: util.Request{
Server: "8.8.4.4",
Port: 53,
Type: dns.TypeA,
Name: "example.com.",
},
@ -81,9 +82,9 @@ func TestResolveAgain(t *testing.T) {
{
util.Options{
Logger: util.InitLogger(0),
Port: 53,
Request: util.Request{
Server: "8.8.4.4",
Port: 53,
Type: dns.TypeAAAA,
Name: "example.com.",
},
@ -93,9 +94,9 @@ func TestResolveAgain(t *testing.T) {
util.Options{
Logger: util.InitLogger(0),
TLS: true,
Port: 853,
Request: util.Request{
Server: "dns.google",
Port: 853,
Type: dns.TypeAAAA,
Name: "example.com.",
},

View File

@ -18,10 +18,210 @@ import (
"gopkg.in/yaml.v3"
)
// ToString turns the response into something that looks a lot like dig
//
// Much of this is taken from https://github.com/miekg/dns/blob/master/msg.go#L900
func ToString(res util.Response, opts util.Options) (string, error) {
if res.DNS == nil {
return "<nil> MsgHdr", errNoMessage
}
var (
s string
opt *dns.OPT
)
if !opts.Short {
if opts.Display.Comments {
s += res.DNS.MsgHdr.String() + " "
s += "QUERY: " + strconv.Itoa(len(res.DNS.Question)) + ", "
s += "ANSWER: " + strconv.Itoa(len(res.DNS.Answer)) + ", "
s += "AUTHORITY: " + strconv.Itoa(len(res.DNS.Ns)) + ", "
s += "ADDITIONAL: " + strconv.Itoa(len(res.DNS.Extra)) + "\n"
opt = res.DNS.IsEdns0()
if opt != nil && opts.Display.Opt {
// OPT PSEUDOSECTION
s += opt.String() + "\n"
}
}
if opts.Display.Question {
if len(res.DNS.Question) > 0 {
if opts.Display.Comments {
s += "\n;; QUESTION SECTION:\n"
}
for _, r := range res.DNS.Question {
str, err := stringParse(r.String(), false, opts)
if err != nil {
return "", fmt.Errorf("%w", err)
}
s += str + "\n"
}
}
}
if opts.Display.Answer {
if len(res.DNS.Answer) > 0 {
if opts.Display.Comments {
s += "\n;; ANSWER SECTION:\n"
}
for _, r := range res.DNS.Answer {
if r != nil {
str, err := stringParse(r.String(), true, opts)
if err != nil {
return "", fmt.Errorf("%w", err)
}
s += str + "\n"
}
}
}
}
if opts.Display.Authority {
if len(res.DNS.Ns) > 0 {
if opts.Display.Comments {
s += "\n;; AUTHORITY SECTION:\n"
}
for _, r := range res.DNS.Ns {
if r != nil {
str, err := stringParse(r.String(), true, opts)
if err != nil {
return "", fmt.Errorf("%w", err)
}
s += str + "\n"
}
}
}
}
if opts.Display.Additional {
if len(res.DNS.Extra) > 0 && (opt == nil || len(res.DNS.Extra) > 1) {
if opts.Display.Comments {
s += "\n;; ADDITIONAL SECTION:\n"
}
for _, r := range res.DNS.Extra {
if r != nil && r.Header().Rrtype != dns.TypeOPT {
str, err := stringParse(r.String(), true, opts)
if err != nil {
return "", fmt.Errorf("%w", err)
}
s += str + "\n"
}
}
}
}
if opts.Display.Statistics {
s += "\n;; Query time: " + res.RTT.String()
s += "\n;; SERVER: " + opts.Request.Server + serverExtra(opts)
s += "\n;; WHEN: " + time.Now().Format(time.RFC1123Z)
s += "\n;; MSG SIZE rcvd: " + strconv.Itoa(res.DNS.Len()) + "\n"
}
} else {
// Print just the responses, nothing else
for i, resp := range res.DNS.Answer {
temp := strings.Split(resp.String(), "\t")
s += temp[len(temp)-1]
if opts.Identify {
s += " from server " + opts.Request.Server + " in " + res.RTT.String()
}
// Don't print newline on last line
if i != len(res.DNS.Answer)-1 {
s += "\n"
}
}
}
return s, nil
}
func serverExtra(opts util.Options) string {
// Add extra information to server string
var extra string
switch {
case opts.TCP:
extra = ":" + strconv.Itoa(opts.Request.Port) + " (TCP)"
case opts.TLS:
extra = ":" + strconv.Itoa(opts.Request.Port) + " (TLS)"
case opts.HTTPS, opts.DNSCrypt:
extra = ""
case opts.QUIC:
extra = ":" + strconv.Itoa(opts.Request.Port) + " (QUIC)"
default:
extra = ":" + strconv.Itoa(opts.Request.Port) + " (UDP)"
}
return extra
}
// stringParse edits the raw responses to user requests.
func stringParse(str string, isAns bool, opts util.Options) (string, error) {
split := strings.Split(str, "\t")
// Make edits if so requested
// TODO: make less ew?
// This exists because the question section should be left alone EXCEPT for punycode.
if isAns {
if !opts.Display.TTL {
// Remove from existence
split = append(split[:1], split[2:]...)
}
if !opts.Display.ShowClass {
// Position depends on if the TTL is there or not.
if opts.Display.TTL {
split = append(split[:2], split[3:]...)
} else {
split = append(split[:1], split[2:]...)
}
}
if opts.Display.TTL && opts.Display.HumanTTL {
ttl, _ := strconv.Atoi(split[1])
split[1] = (time.Duration(ttl) * time.Second).String()
}
}
if opts.Display.UcodeTranslate {
var (
err error
semi string
)
if strings.HasPrefix(split[0], ";") {
split[0] = strings.TrimPrefix(split[0], ";")
semi = ";"
}
split[0], err = idna.ToUnicode(split[0])
if err != nil {
return "", fmt.Errorf("punycode: %w", err)
}
split[0] = semi + split[0]
}
return strings.Join(split, "\t"), nil
}
// PrintSpecial is for printing as JSON, XML or YAML.
// As of now JSON and XML use the stdlib version.
func PrintSpecial(msg *dns.Msg, opts util.Options) (string, error) {
formatted, err := MakePrintable(msg, opts)
func PrintSpecial(res util.Response, opts util.Options) (string, error) {
formatted, err := MakePrintable(res, opts)
if err != nil {
return "", err
}
@ -52,11 +252,34 @@ func PrintSpecial(msg *dns.Msg, opts util.Options) (string, error) {
// MakePrintable takes a DNS message and makes it nicer to be printed as JSON,YAML,
// and XML. Little is changed beyond naming.
func MakePrintable(msg *dns.Msg, opts util.Options) (*Message, error) {
var err error
func MakePrintable(res util.Response, opts util.Options) (*Message, error) {
var (
err error
msg = res.DNS
)
// The things I do for compatibility
ret := Message{
Header: msg.MsgHdr,
Header: Header{
ID: msg.Id,
Response: msg.Response,
Opcode: dns.OpcodeToString[msg.Opcode],
Authoritative: msg.Authoritative,
Truncated: msg.Truncated,
RecursionDesired: msg.RecursionDesired,
RecursionAvailable: msg.RecursionAvailable,
Zero: msg.Zero,
AuthenticatedData: msg.AuthenticatedData,
CheckingDisabled: msg.CheckingDisabled,
Status: dns.RcodeToString[msg.Rcode],
},
}
opt := msg.IsEdns0()
if opt != nil && opts.Display.Opt {
ret.Opt, err = parseOpt(*opt)
if err != nil {
return nil, fmt.Errorf("edns print: %w", err)
}
}
for _, question := range msg.Question {
@ -85,8 +308,8 @@ func MakePrintable(msg *dns.Msg, opts util.Options) (*Message, error) {
name string
)
if opts.ShowTTL {
if opts.HumanTTL {
if opts.Display.TTL {
if opts.Display.HumanTTL {
ttl = (time.Duration(answer.Header().Ttl) * time.Second).String()
} else {
ttl = strconv.Itoa(int(answer.Header().Ttl))
@ -122,8 +345,8 @@ func MakePrintable(msg *dns.Msg, opts util.Options) (*Message, error) {
name string
)
if opts.ShowTTL {
if opts.HumanTTL {
if opts.Display.TTL {
if opts.Display.HumanTTL {
ttl = (time.Duration(ns.Header().Ttl) * time.Second).String()
} else {
ttl = strconv.Itoa(int(ns.Header().Ttl))
@ -139,7 +362,7 @@ func MakePrintable(msg *dns.Msg, opts util.Options) (*Message, error) {
name = ns.Header().Name
}
ret.Ns = append(ret.Ns, Answer{
ret.Authority = append(ret.Authority, Answer{
RRHeader: RRHeader{
Name: name,
Type: dns.TypeToString[ns.Header().Rrtype],
@ -162,8 +385,8 @@ func MakePrintable(msg *dns.Msg, opts util.Options) (*Message, error) {
name string
)
if opts.ShowTTL {
if opts.HumanTTL {
if opts.Display.TTL {
if opts.Display.HumanTTL {
ttl = (time.Duration(additional.Header().Ttl) * time.Second).String()
} else {
ttl = strconv.Itoa(int(additional.Header().Ttl))
@ -192,12 +415,15 @@ func MakePrintable(msg *dns.Msg, opts util.Options) (*Message, error) {
}
}
opt := msg.IsEdns0()
if opt != nil && opts.Display.Opt {
ret.Opt, err = parseOpt(*opt)
if err != nil {
return nil, fmt.Errorf("edns print: %w", err)
if opts.Display.Statistics {
ret.Statistics = Statistics{
RTT: res.RTT.String(),
Server: opts.Request.Server + serverExtra(opts),
When: time.Now().Format(time.RFC1123Z),
MsgSize: res.DNS.Len(),
}
} else {
ret.Statistics = Statistics{}
}
return &ret, nil

View File

@ -16,15 +16,16 @@ func TestRealPrint(t *testing.T) {
opts := []util.Options{
{
Logger: util.InitLogger(0),
Port: 53,
TCP: true,
ShowQuery: true,
RD: true,
ShowTTL: true,
HumanTTL: true,
JSON: true,
Display: util.Displays{
Logger: util.InitLogger(0),
TCP: true,
HeaderFlags: util.HeaderFlags{
RD: true,
},
JSON: true,
Display: util.Display{
Comments: true,
Question: true,
Answer: true,
@ -32,9 +33,13 @@ func TestRealPrint(t *testing.T) {
Additional: true,
Statistics: true,
UcodeTranslate: true,
TTL: true,
HumanTTL: true,
ShowQuery: true,
},
Request: util.Request{
Server: "a.gtld-servers.net",
Port: 53,
Type: dns.StringToType["NS"],
Class: 1,
Name: "google.com.",
@ -44,17 +49,18 @@ func TestRealPrint(t *testing.T) {
},
},
{
Logger: util.InitLogger(0),
Port: 53,
TCP: true,
ShowQuery: true,
RD: true,
Logger: util.InitLogger(0),
TCP: true,
HeaderFlags: util.HeaderFlags{
RD: true,
},
Verbosity: 0,
ShowTTL: true,
Short: true,
Identify: true,
YAML: false,
Display: util.Displays{
Short: true,
Identify: true,
YAML: false,
Display: util.Display{
Comments: true,
Question: true,
Answer: true,
@ -62,9 +68,12 @@ func TestRealPrint(t *testing.T) {
Additional: true,
Statistics: true,
UcodeTranslate: true,
TTL: true,
ShowQuery: true,
},
Request: util.Request{
Server: "ns1.google.com",
Port: 53,
Type: dns.StringToType["NS"],
Class: 1,
Name: "google.com.",
@ -76,16 +85,14 @@ func TestRealPrint(t *testing.T) {
},
},
{
Logger: util.InitLogger(0),
Port: 53,
HTTPS: true,
ShowQuery: true,
RD: true,
ShowTTL: true,
HumanTTL: true,
Identify: true,
XML: true,
Display: util.Displays{
Logger: util.InitLogger(0),
HTTPS: true,
HeaderFlags: util.HeaderFlags{
RD: true,
},
Identify: true,
XML: true,
Display: util.Display{
Comments: true,
Question: true,
Answer: true,
@ -93,9 +100,13 @@ func TestRealPrint(t *testing.T) {
Additional: true,
Statistics: true,
UcodeTranslate: false,
TTL: true,
HumanTTL: true,
ShowQuery: true,
},
Request: util.Request{
Server: "https://dns.froth.zone/dns-query",
Port: 443,
Type: dns.StringToType["NS"],
Class: 1,
Name: "freecumextremist.com.",
@ -108,14 +119,13 @@ func TestRealPrint(t *testing.T) {
},
},
{
Logger: util.InitLogger(0),
Port: 853,
TLS: true,
ShowQuery: true,
RD: true,
Logger: util.InitLogger(0),
TLS: true,
HeaderFlags: util.HeaderFlags{
RD: true,
},
Verbosity: 0,
ShowTTL: false,
Display: util.Displays{
Display: util.Display{
Comments: true,
Question: true,
Answer: true,
@ -123,25 +133,29 @@ func TestRealPrint(t *testing.T) {
Additional: true,
Statistics: true,
UcodeTranslate: true,
TTL: false,
ShowQuery: true,
},
Request: util.Request{
Server: "dns.google",
Port: 853,
Type: dns.StringToType["NS"],
Class: 1,
Name: "freecumextremist.com.",
},
},
{
Logger: util.InitLogger(0),
Port: 53,
TCP: true,
ShowQuery: true,
AA: true,
RD: true,
Logger: util.InitLogger(0),
TCP: true,
HeaderFlags: util.HeaderFlags{
AA: true,
RD: true,
},
Verbosity: 0,
ShowTTL: true,
YAML: true,
Display: util.Displays{
YAML: true,
Display: util.Display{
Comments: true,
Question: true,
Answer: true,
@ -149,9 +163,12 @@ func TestRealPrint(t *testing.T) {
Additional: true,
Statistics: true,
UcodeTranslate: false,
TTL: true,
ShowQuery: true,
},
Request: util.Request{
Server: "rin.froth.zone",
Port: 53,
Type: dns.StringToType["A"],
Class: 1,
Name: "froth.zone.",
@ -176,7 +193,7 @@ func TestRealPrint(t *testing.T) {
if test.JSON || test.XML || test.YAML {
str := ""
str, err = query.PrintSpecial(resp.DNS, test)
str, err = query.PrintSpecial(resp, test)
assert.NilError(t, err)
assert.Assert(t, str != "")
}
@ -190,7 +207,7 @@ func TestRealPrint(t *testing.T) {
func TestBadFormat(t *testing.T) {
t.Parallel()
_, err := query.PrintSpecial(new(dns.Msg), util.Options{})
_, err := query.PrintSpecial(util.Response{DNS: new(dns.Msg)}, util.Options{})
assert.ErrorContains(t, err, "never happen")
}

View File

@ -102,7 +102,7 @@ func CreateQuery(opts util.Options) (util.Response, error) {
opts.Logger.Debug(req)
if !opts.Short {
if opts.ShowQuery {
if opts.Display.ShowQuery {
opts.Logger.Info("Printing constructed query")
var (
@ -111,7 +111,7 @@ func CreateQuery(opts util.Options) (util.Response, error) {
)
if opts.JSON || opts.XML || opts.YAML {
str, err = PrintSpecial(req, opts)
str, err = PrintSpecial(util.Response{DNS: req}, opts)
if err != nil {
return util.Response{}, err
}
@ -133,7 +133,7 @@ func CreateQuery(opts util.Options) (util.Response, error) {
fmt.Println(str)
opts.ShowQuery = false
opts.Display.ShowQuery = false
}
}

View File

@ -16,18 +16,20 @@ func TestCreateQ(t *testing.T) {
in := []util.Options{
{
Logger: util.InitLogger(0),
Port: 53,
Z: true,
ShowQuery: true,
YAML: true,
Logger: util.InitLogger(0),
HeaderFlags: util.HeaderFlags{
Z: true,
},
YAML: true,
Request: util.Request{
Server: "8.8.4.4",
Port: 53,
Type: dns.TypeA,
Name: "example.com.",
},
Display: util.Displays{
Display: util.Display{
Comments: true,
Question: true,
Opt: true,
@ -35,6 +37,7 @@ func TestCreateQ(t *testing.T) {
Authority: true,
Additional: true,
Statistics: true,
ShowQuery: true,
},
EDNS: util.EDNS{
ZFlag: 1,
@ -50,18 +53,19 @@ func TestCreateQ(t *testing.T) {
},
},
{
Logger: util.InitLogger(0),
Port: 53,
Z: true,
ShowQuery: true,
XML: true,
Logger: util.InitLogger(0),
HeaderFlags: util.HeaderFlags{
Z: true,
},
XML: true,
Request: util.Request{
Server: "8.8.4.4",
Port: 53,
Type: dns.TypeA,
Name: "example.com.",
},
Display: util.Displays{
Display: util.Display{
Comments: true,
Question: true,
Opt: true,
@ -70,22 +74,21 @@ func TestCreateQ(t *testing.T) {
Additional: true,
Statistics: true,
UcodeTranslate: true,
ShowQuery: true,
},
},
{
Logger: util.InitLogger(0),
Port: 853,
// Z: true,
ShowQuery: true,
JSON: true,
QUIC: true,
JSON: true,
QUIC: true,
Request: util.Request{
Server: "dns.adguard.com",
Port: 853,
Type: dns.TypeA,
Name: "example.com.",
},
Display: util.Displays{
Display: util.Display{
Comments: true,
Question: true,
Opt: true,
@ -93,6 +96,7 @@ func TestCreateQ(t *testing.T) {
Authority: true,
Additional: true,
Statistics: true,
ShowQuery: true,
},
EDNS: util.EDNS{
EnableEDNS: true,
@ -113,7 +117,7 @@ func TestCreateQ(t *testing.T) {
assert.NilError(t, err)
assert.Assert(t, res != util.Response{})
str, err := query.PrintSpecial(res.DNS, opt)
str, err := query.PrintSpecial(res, opt)
assert.NilError(t, err)
assert.Assert(t, str != "")

View File

@ -31,7 +31,7 @@ func LoadResolver(opts util.Options) (Resolver, error) {
}, nil
case opts.QUIC:
opts.Logger.Info("loading DNS-over-QUIC resolver")
opts.Request.Server = net.JoinHostPort(opts.Request.Server, strconv.Itoa(opts.Port))
opts.Request.Server = net.JoinHostPort(opts.Request.Server, strconv.Itoa(opts.Request.Port))
return &QUICResolver{
opts: opts,
@ -48,7 +48,7 @@ func LoadResolver(opts util.Options) (Resolver, error) {
}, nil
default:
opts.Logger.Info("loading standard/DNS-over-TLS resolver")
opts.Request.Server = net.JoinHostPort(opts.Request.Server, strconv.Itoa(opts.Port))
opts.Request.Server = net.JoinHostPort(opts.Request.Server, strconv.Itoa(opts.Request.Port))
return &StandardResolver{
opts: opts,

View File

@ -4,48 +4,63 @@ package query
import (
"errors"
"fmt"
"strconv"
"strings"
"time"
"git.froth.zone/sam/awl/util"
"github.com/miekg/dns"
"golang.org/x/net/idna"
)
// Message is for overall DNS responses.
//
//nolint:govet // Better output is worth 32 bytes.
//nolint:govet // Better looking output is worth a few bytes.
type Message struct {
Header dns.MsgHdr `json:"header,omitempty" xml:"header,omitempty" yaml:",omitempty"`
Opt []Opts `json:"opt,omitempty" xml:"opt,omitempty" yaml:"opt,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"`
Additional []Answer `json:"additional,omitempty" xml:"additional,omitempty" yaml:",omitempty"`
// Header section
Header `json:"header,omitempty" xml:"header,omitempty" yaml:"header,omitempty"`
// Opt Pseudosection
Opt []Opts `json:"opt,omitempty" xml:"opt,omitempty" yaml:"opt,omitempty"`
// Question Section
Question []Question `json:"question,omitempty" xml:"question,omitempty" yaml:"question,omitempty"`
// Answer Section
Answer []Answer `json:"answer,omitempty" xml:"answer,omitempty" yaml:"answer,omitempty"`
// Authority Section
Authority []Answer `json:"authority,omitempty" xml:"authority,omitempty" yaml:"authority,omitempty"`
// Additional Section
Additional []Answer `json:"additional,omitempty" xml:"additional,omitempty" yaml:"additional,omitempty"`
// Statistics :)
Statistics `json:"statistics,omitempty" xml:"statistics,omitempty" yaml:"statistics,omitempty"`
}
// Header is the header.
type Header struct {
Opcode string `json:"opcode," xml:"opcode," yaml:"opcode" example:"QUERY"`
Status string `json:"status," xml:"status," yaml:"status" example:"NOERR"`
ID uint16 `json:"id," xml:"id," yaml:"id" example:"12"`
Response bool `json:"response," xml:"response," yaml:"response" example:"true"`
Authoritative bool `json:"authoritative," xml:"authoritative," yaml:"authoritative" example:"false"`
Truncated bool `json:"truncated," xml:"truncated," yaml:"truncated" example:"false"`
RecursionDesired bool `json:"recursionDesired," xml:"recursionDesired," yaml:"recursionDesired" example:"true"`
RecursionAvailable bool `json:"recursionAvailable," xml:"recursionAvailable," yaml:"recursionAvailable" example:"true"`
Zero bool `json:"zero," xml:"zero," yaml:"zero" example:"false"`
AuthenticatedData bool `json:"authenticatedData," xml:"authenticatedData," yaml:"authenticatedData" example:"false"`
CheckingDisabled bool `json:"checkingDisabled," xml:"checkingDisabled," yaml:"checkingDisabled" example:"false"`
}
// Question is a DNS Query.
type Question struct {
Name string `json:"name,omitempty" xml:"name,omitempty" yaml:",omitempty"`
Class string `json:"class,omitempty" xml:"class,omitempty" yaml:",omitempty"`
Type string `json:"type,omitempty" xml:"type,omitempty" yaml:",omitempty"`
Name string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty" example:"localhost"`
Class string `json:"class,omitempty" xml:"class,omitempty" yaml:"class,omitempty" example:"A"`
Type string `json:"type,omitempty" xml:"type,omitempty" yaml:"type,omitempty" example:"IN"`
}
// RRHeader is for DNS Resource Headers.
type RRHeader struct {
Name string `json:"name,omitempty" xml:"name,omitempty" yaml:",omitempty"`
TTL string `json:"ttl,omitempty" xml:"ttl,omitempty" yaml:",omitempty"`
Class string `json:"class,omitempty" xml:"class,omitempty" yaml:",omitempty"`
Type string `json:"type,omitempty" xml:"type,omitempty" yaml:",omitempty"`
Name string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty" example:"127.0.0.1"`
TTL string `json:"ttl,omitempty" xml:"ttl,omitempty" yaml:"ttl,omitempty" example:"0ms"`
Class string `json:"class,omitempty" xml:"class,omitempty" yaml:"class,omitempty" example:"A"`
Type string `json:"type,omitempty" xml:"type,omitempty" yaml:"type,omitempty" example:"IN"`
Rdlength uint16 `json:"-" xml:"-" yaml:"-"`
}
// Opts is for the OPT pseudosection, nearly exclusively for EDNS.
type Opts struct {
Name string `json:"name,omitempty" xml:"name,omitempty" yaml:",omitempty"`
Value string `json:"value" xml:"value" yaml:""`
Name string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty"`
Value string `json:"value" xml:"value" yaml:"value"`
}
// Answer is for a DNS Response.
@ -54,199 +69,12 @@ type Answer struct {
RRHeader `json:"header,omitempty" xml:"header,omitempty" yaml:"header,omitempty"`
}
// ToString turns the response into something that looks a lot like dig
//
// Much of this is taken from https://github.com/miekg/dns/blob/master/msg.go#L900
func ToString(res util.Response, opts util.Options) (string, error) {
if res.DNS == nil {
return "<nil> MsgHdr", errNoMessage
}
var (
s string
opt *dns.OPT
)
if !opts.Short {
if opts.Display.Comments {
s += res.DNS.MsgHdr.String() + " "
s += "QUERY: " + strconv.Itoa(len(res.DNS.Question)) + ", "
s += "ANSWER: " + strconv.Itoa(len(res.DNS.Answer)) + ", "
s += "AUTHORITY: " + strconv.Itoa(len(res.DNS.Ns)) + ", "
s += "ADDITIONAL: " + strconv.Itoa(len(res.DNS.Extra)) + "\n"
opt = res.DNS.IsEdns0()
if opt != nil && opts.Display.Opt {
// OPT PSEUDOSECTION
s += opt.String() + "\n"
}
}
if opts.Display.Question {
if len(res.DNS.Question) > 0 {
if opts.Display.Comments {
s += "\n;; QUESTION SECTION:\n"
}
for _, r := range res.DNS.Question {
str, err := stringParse(r.String(), false, opts)
if err != nil {
return "", fmt.Errorf("%w", err)
}
s += str + "\n"
}
}
}
if opts.Display.Answer {
if len(res.DNS.Answer) > 0 {
if opts.Display.Comments {
s += "\n;; ANSWER SECTION:\n"
}
for _, r := range res.DNS.Answer {
if r != nil {
str, err := stringParse(r.String(), true, opts)
if err != nil {
return "", fmt.Errorf("%w", err)
}
s += str + "\n"
}
}
}
}
if opts.Display.Authority {
if len(res.DNS.Ns) > 0 {
if opts.Display.Comments {
s += "\n;; AUTHORITY SECTION:\n"
}
for _, r := range res.DNS.Ns {
if r != nil {
str, err := stringParse(r.String(), true, opts)
if err != nil {
return "", fmt.Errorf("%w", err)
}
s += str + "\n"
}
}
}
}
if opts.Display.Additional {
if len(res.DNS.Extra) > 0 && (opt == nil || len(res.DNS.Extra) > 1) {
if opts.Display.Comments {
s += "\n;; ADDITIONAL SECTION:\n"
}
for _, r := range res.DNS.Extra {
if r != nil && r.Header().Rrtype != dns.TypeOPT {
str, err := stringParse(r.String(), true, opts)
if err != nil {
return "", fmt.Errorf("%w", err)
}
s += str + "\n"
}
}
}
}
if opts.Display.Statistics {
s += "\n;; Query time: " + res.RTT.String()
// Add extra information to server string
var extra string
switch {
case opts.TCP:
extra = ":" + strconv.Itoa(opts.Port) + " (TCP)"
case opts.TLS:
extra = ":" + strconv.Itoa(opts.Port) + " (TLS)"
case opts.HTTPS, opts.DNSCrypt:
extra = ""
case opts.QUIC:
extra = ":" + strconv.Itoa(opts.Port) + " (QUIC)"
default:
extra = ":" + strconv.Itoa(opts.Port) + " (UDP)"
}
s += "\n;; SERVER: " + opts.Request.Server + extra
s += "\n;; WHEN: " + time.Now().Format(time.RFC1123Z)
s += "\n;; MSG SIZE rcvd: " + strconv.Itoa(res.DNS.Len()) + "\n"
}
} else {
// Print just the responses, nothing else
for i, resp := range res.DNS.Answer {
temp := strings.Split(resp.String(), "\t")
s += temp[len(temp)-1]
if opts.Identify {
s += " from server " + opts.Request.Server + " in " + res.RTT.String()
}
// Don't print newline on last line
if i != len(res.DNS.Answer)-1 {
s += "\n"
}
}
}
return s, nil
}
func stringParse(str string, isAns bool, opts util.Options) (string, error) {
split := strings.Split(str, "\t")
// Make edits if so requested
// TODO: make less ew?
// This exists because the question section should be left alone EXCEPT for punycode.
if isAns {
if !opts.ShowTTL {
// Remove from existence
split = append(split[:1], split[2:]...)
}
if !opts.ShowClass {
// Position depends on if the TTL is there or not.
if opts.ShowTTL {
split = append(split[:2], split[3:]...)
} else {
split = append(split[:1], split[2:]...)
}
}
if opts.ShowTTL && opts.HumanTTL {
ttl, _ := strconv.Atoi(split[1])
split[1] = (time.Duration(ttl) * time.Second).String()
}
}
if opts.Display.UcodeTranslate {
var (
err error
semi string
)
if strings.HasPrefix(split[0], ";") {
split[0] = strings.TrimPrefix(split[0], ";")
semi = ";"
}
split[0], err = idna.ToUnicode(split[0])
if err != nil {
return "", fmt.Errorf("punycode: %w", err)
}
split[0] = semi + split[0]
}
return strings.Join(split, "\t"), nil
// Statistics is the little bit at the bottom :).
type Statistics struct {
RTT string `json:"queryTime,omitempty" xml:"queryTime,omitempty" yaml:"queryTime,omitempty"`
Server string `json:"server,omitempty" xml:"server,omitempty" yaml:"server,omitempty"`
When string `json:"when,omitempty" xml:"when,omitempty" yaml:"when,omitempty"`
MsgSize int `json:"msgSize,omitempty" xml:"msgSize,omitempty" yaml:"msgSize,omitempty"`
}
var errNoMessage = errors.New("no message")

View File

@ -1,7 +1,7 @@
# SPDX-License-Identifier: BSD-3-Clause
# Template for the BSD/GNU makefiles
HASH ?= `git describe --always --dirty --broken | sed 's/\([^-]*-g\)/r\1/;s/-/./g' || echo "UNKNOWN"`
HASH ?= `git describe --always --dirty --broken | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g' || echo "UNKNOWN"`
SOURCES ?= $(shell find . -name "*.go" -type f ! -name '*_test*')
TEST_SOURCES ?= $(shell find . -name "*_test.go" -type f)
@ -90,4 +90,4 @@ clean:
.PHONY: help
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/^/ /'

View File

@ -12,67 +12,134 @@ import (
// Options is the grand structure for all query options.
type Options struct {
Logger *logawl.Logger
TLSHost string
// The logger
Logger *logawl.Logger `json:"-"`
// Host to verify TLS cert with
TLSHost string `json:"tlsHost" example:""`
// EDNS Options
EDNS
Request Request
Port int
Verbosity int
Display Displays
TC bool
ShowTTL bool
ShowClass bool
ShowQuery bool
AA bool
AD bool
CD bool
QR bool
RD bool
RA bool
IPv4 bool
Z bool
Reverse bool
HumanTTL bool
Truncate bool
Short bool
Identify bool
JSON bool
XML bool
YAML bool
QUIC bool
HTTPS bool
TLSNoVerify bool
TLS bool
DNSCrypt bool
TCP bool
IPv6 bool
// DNS request :)
Request Request
// Verbosity levels, see [logawl.AllLevels]
Verbosity int `json:"-" example:"0"`
// Display options
Display Display
// Ignore Truncation
Truncate bool `json:"ignoreTruncate" example:"false"`
// Print only the answer
Short bool `json:"short" example:"false"`
// When Short is true, display where the query came from
Identify bool `json:"identify" example:"false"`
// Perform a reverse DNS query when true
Reverse bool `json:"reverse" example:"false"`
HeaderFlags
// Display resposne as JSON
JSON bool `json:"-" xml:"-" yaml:"-"`
// Display response as XML
XML bool `json:"-" xml:"-" yaml:"-"`
// Display response as YAML
YAML bool `json:"-" xml:"-" yaml:"-"`
// Use TCP instead of UDP to make the query
TCP bool `json:"tcp" example:"false"`
// Use DNS-over-TLS to make the query
TLS bool `json:"dnsOverTLS" example:"false"`
// When using TLS, ignore certificates
TLSNoVerify bool `json:"tlsNoVerify" example:"false"`
// Use DNS-over-HTTPS to make the query
HTTPS bool `json:"dnsOverHTTPS" example:"false"`
// Use DNS-over-QUIC to make the query
//nolint:tagliatelle // QUIC is an acronym
QUIC bool `json:"dnsOverQUIC" example:"false"`
// Use DNSCrypt to make the query
DNSCrypt bool `json:"dnscrypt" example:"false"`
// Force IPv4 only
IPv4 bool `json:"forceIPv4" example:"false"`
// Force IPv6 only
IPv6 bool `json:"forceIPv6" example:"false"`
}
// Displays contains toggles for what to (and not to) display.
type Displays struct {
Comments bool
Question bool // QUESTION SECTION
Opt bool // OPT PSEUDOSECTION
Answer bool // ANSWER SECTION
Authority bool // AUTHORITY SECTION
Additional bool // ADDITIONAL SECTION
Statistics bool // Query time, message size, etc.
UcodeTranslate bool // Translate Punycode back to Unicode
// HeaderFlags are the flags that are in DNS headers.
type HeaderFlags struct {
// Authoritative Answer DNS query flag
AA bool `json:"authoritative" example:"false"`
// Authenticated Data DNS query flag
AD bool `json:"authenticatedData" example:"false"`
// Checking Disabled DNS query flag
CD bool `json:"checkingDisabled" example:"false"`
// QueRy DNS query flag
QR bool `json:"query" example:"false"`
// Recursion Desired DNS query flag
RD bool `json:"recursionDesired" example:"true"`
// Recursion Available DNS query flag
RA bool `json:"recursionAvailable" example:"false"`
// TrunCated DNS query flag
TC bool `json:"truncated" example:"false"`
// Zero DNS query flag
Z bool `json:"zero" example:"false"`
}
// Display contains toggles for what to (and not to) display.
type Display struct {
/* Section displaying */
// Comments?
Comments bool `json:"comments" example:"true"`
// QUESTION SECTION
Question bool `json:"question" example:"true"`
// OPT PSEUDOSECTION
Opt bool `json:"opt" example:"true"`
// ANSWER SECTION
Answer bool `json:"answer" example:"true"`
// AUTHORITY SECTION
Authority bool `json:"authority" example:"true"`
// ADDITIONAL SECTION
Additional bool `json:"additional" example:"true"`
// Query time, message size, etc.
Statistics bool `json:"statistics" example:"true"`
// Display TTL in response
TTL bool `json:"ttl" example:"true"`
/* Answer formatting */
// Display Class in response
ShowClass bool `json:"showClass" example:"true"`
// Display query before it is sent
ShowQuery bool `json:"showQuery" example:"false"`
// Display TTL as human-readable
HumanTTL bool `json:"humanTTL" example:"false"`
// Translate Punycode back to Unicode
UcodeTranslate bool `json:"unicode" example:"true"`
}
// EDNS contains toggles for various EDNS options.
type EDNS struct {
Subnet dns.EDNS0_SUBNET
ZFlag uint16
BufSize uint16
EnableEDNS bool
Cookie bool
DNSSEC bool
Expire bool
KeepOpen bool
Nsid bool
Padding bool
Version uint8
// Subnet to originate query from.
Subnet dns.EDNS0_SUBNET `json:"subnet"`
// Must Be Zero flag
ZFlag uint16 `json:"zflag" example:"0"`
// UDP buffer size
BufSize uint16 `json:"bufSize" example:"1232"`
// Enable/Disable EDNS entirely
EnableEDNS bool `json:"edns" example:"false"`
// Sending EDNS cookie
Cookie bool `json:"cookie" example:"true"`
// Enabling DNSSEC
DNSSEC bool `json:"dnssec" example:"false"`
// Sending EDNS Expire
Expire bool `json:"expire" example:"false"`
// Sending EDNS TCP keepopen
KeepOpen bool `json:"keepOpen" example:"false"`
// Sending EDNS NSID
Nsid bool `json:"nsid" example:"false"`
// Send EDNS Padding
Padding bool `json:"padding" example:"false"`
// Set EDNS version (default: 0)
Version uint8 `json:"version" example:"0"`
}
// ParseSubnet takes a subnet argument and makes it into one that the DNS library

View File

@ -10,16 +10,26 @@ import (
// Response is the DNS response.
type Response struct {
DNS *dns.Msg // The full DNS response
RTT time.Duration `json:"rtt"` // The time it took to make the DNS query
// The full DNS response
DNS *dns.Msg `json:"response"`
// The time it took to make the DNS query
RTT time.Duration `json:"rtt" example:"2000000000"`
}
// Request is a structure for a DNS query.
type Request struct {
Server string `json:"server"`
Name string `json:"name"`
Timeout time.Duration
Retries int
Type uint16 `json:"request"`
Class uint16 `json:"class"`
// Server to query
Server string `json:"server" example:"1.0.0.1"`
// Domain to query
Name string `json:"name" example:"example.com"`
// Duration to wait until marking request as failed
Timeout time.Duration `json:"timeout" example:"2000000000"`
// Port to make DNS request on
Port int `json:"port" example:"53"`
// Number of failures to make before giving up
Retries int `json:"retries" example:"2"`
// Request type, eg. A, AAAA, NAPTR
Type uint16 `json:"type" example:"1"`
// Request class, eg. IN
Class uint16 `json:"class" example:"1"`
}