diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..f1f5f5e --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${fileDirname}", + "args": ["+timeout=1"] + } + ] +} \ No newline at end of file diff --git a/cli/cli.go b/cli/cli.go index f483aed..80dc2a7 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -46,6 +46,7 @@ func ParseCLI(version string) (Options, error) { reverse = flag.Bool("reverse", false, "do a reverse lookup", flag.OptShorthand('x')) timeout = flag.Float32("timeout", 1, "Timeout, in `seconds`") + retry = flag.Int("retry", 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)") @@ -118,6 +119,7 @@ func ParseCLI(version string) (Options, error) { Class: dns.StringToClass[strings.ToUpper(*class)], Name: *query, Timeout: time.Duration(*timeout * float32(time.Second)), + Retries: *retry, }, Display: Displays{ Question: !*noQ, @@ -128,6 +130,15 @@ func ParseCLI(version string) (Options, error) { }, } + // Set timeout to 0.5 seconds if set below 0.5 + if float32(opts.Request.Timeout) < (0.5 * float32(time.Second)) { + opts.Request.Timeout = (time.Second / 2) + } + + if opts.Request.Retries < 0 { + opts.Request.Timeout = 0 + } + opts.Logger.Info("POSIX flags parsed") opts.Logger.Debug(fmt.Sprintf("%+v", opts)) @@ -140,7 +151,7 @@ func ParseCLI(version string) (Options, error) { // This includes the dig-style (+) options err = ParseMiscArgs(flag.Args(), &opts) if err != nil { - opts.Logger.Error(err) + opts.Logger.Warn(err) return opts, err } opts.Logger.Info("Dig/Drill flags parsed") diff --git a/cli/dig.go b/cli/dig.go index 458476e..32b4193 100644 --- a/cli/dig.go +++ b/cli/dig.go @@ -4,14 +4,16 @@ package cli import ( "fmt" + "strconv" "strings" + "time" ) // Parse dig-like commands and set the options as such. func ParseDig(arg string, opts *Options) error { // returns true if the flag starts with a no isNo := !strings.HasPrefix(arg, "no") - opts.Logger.Info("Setting", arg, "to", isNo) + opts.Logger.Info("Setting", arg) switch arg { // Set DNS query flags @@ -33,12 +35,22 @@ func ParseDig(arg string, opts *Options) error { opts.Z = isNo // End DNS query flags + // DNS-over-X case "dnssec", "nodnssec": opts.DNSSEC = isNo case "tcp", "vc", "notcp", "novc": opts.TCP = isNo case "ignore", "noignore": opts.Truncate = isNo + case "tls", "notls": + opts.TLS = isNo + case "dnscrypt", "nodnscrypt": + opts.DNSCrypt = isNo + case "https", "nohttps": + opts.HTTPS = isNo + case "quic", "noquic": + opts.QUIC = isNo + // End DNS-over-X // Formatting case "short", "noshort": @@ -74,7 +86,32 @@ func ParseDig(arg string, opts *Options) error { opts.Display.Statistics = isNo default: - return fmt.Errorf("dig: unknown flag %s given", arg) + // Recursive switch statements WOO + switch { + case strings.HasPrefix(arg, "timeout"): + timeout, err := strconv.Atoi(strings.Split(arg, "=")[1]) + + if err != nil { + return fmt.Errorf("dig: Invalid timeout value") + } + + opts.Request.Timeout = time.Duration(timeout) + + case strings.HasPrefix(arg, "retry"), strings.HasPrefix(arg, "tries"): + tries, err := strconv.Atoi(strings.Split(arg, "=")[1]) + if err != nil { + return fmt.Errorf("dig: Invalid retry value") + } + + if strings.HasPrefix(arg, "tries") { + tries++ + } + + opts.Request.Retries = tries + + default: + return fmt.Errorf("dig: unknown flag %s given", arg) + } } return nil } diff --git a/cli/dig_test.go b/cli/dig_test.go index 71e69ec..4b12db4 100644 --- a/cli/dig_test.go +++ b/cli/dig_test.go @@ -24,6 +24,10 @@ func FuzzDig(f *testing.F) { "dnssec", "nodnssec", "tcp", "vc", "notcp", "novc", "ignore", "noignore", + "tls", "notls", + "dnscrypt", "nodnscrypt", + "https", "nohttps", + "quic", "noquic", "short", "noshort", "json", "nojson", "xml", "noxml", diff --git a/cli/misc.go b/cli/misc.go index 6f0f747..c2bbb94 100644 --- a/cli/misc.go +++ b/cli/misc.go @@ -103,7 +103,7 @@ func ParseMiscArgs(args []string, opts *Options) error { resolv, err := conf.GetDNSConfig() if err != nil { // :^) - opts.Logger.Error("Could not query system for server. Using default") + opts.Logger.Warn("Could not query system for server. Using default") opts.Request.Server = "95.216.99.249" } else { // Make sure that if IPv4 or IPv6 is asked for it actually uses it diff --git a/doc/awl.1 b/doc/awl.1 index 7a36e25..a2246e3 100644 --- a/doc/awl.1 +++ b/doc/awl.1 @@ -1,232 +1,264 @@ -.\" Generated by scdoc 1.11.2 -.\" Complete documentation for this program is not available as a GNU info page -.ie \n(.g .ds Aq \(aq -.el .ds Aq ' -.nh -.ad l -.\" Begin generated content: -.TH "awl" "1" "2022-07-22" -.PP -.SH NAME -awl - DNS lookup tool -.PP -.SH SYNOPSIS -\fBawl\fR [ \fIOPTIONS\fR ] \fIname\fR [ \fI@server\fR ] [ \fItype\fR ] -.br -where -.PP -\fIname\fR is the query to make (\fBexample: froth.\&zone\fR) -.br -\fI@server\fR is the server to query (\fBexample: dns.\&froth.\&zone\fR) -.br -\fItype\fR is the DNS resource type (\fBexample: AAAA\fR) -.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, -typically used in tannery (leatherworking).\& -.PP -\fBawl\fR is designed to be a more "modern" version of \fIdrill\fR(1) by including -some more recent RFCs and output options.\& \fBawl\fR is still heavily -Work-In-Progress so some features may get added or removed.\& -.PP -.SH OPTIONS -.RS 4 -Dig-like +[no]flags are supported, see dig(1) -.PP -\fB-D\fR, \fB--dnssec\fR, \fB+dnssec\fR -.br - Enable DNSSEC.\& This needs to be manually enabled.\& -.PP -\fB-v\fR \fIvalue\fR -.br - Set verbosity (currently WIP) -.PP -\fB-V\fR -.br - Print the version and exit.\& -.PP -\fB-h\fR -.br - Show a "short" help message.\& -.PP -.RE -.SS Query Options -.RS 4 -\fB-4\fR -.br - Only make query over IPv4 -.PP -\fB-6\fR -.br - Only make query over IPv6 -.PP -\fB-p\fR, \fB--port\fR \fIport\fR -.br - Sets the port to query.\& -.br - -.br -\fIDefault Ports\fR: -.RS 4 -.PD 0 -.IP \(bu 4 -\fI53\fR for \fBUDP\fR and \fBTCP\fR -.IP \(bu 4 -\fI853\fR for \fBTLS\fR and \fBQUIC\fR -.IP \(bu 4 -\fI443\fR for \fBHTTPS\fR -.PD -.PP -.RE -\fB-q\fR, \fB--query\fR \fIdomain\fR -.br - Domain to query (eg.\& example.\&com) -.PP -\fB-c\fR, \fB--class\fR \fIclass\fR -.br - DNS class to query (eg.\& IN, CH) -.PP -\fB-t\fR, \fB--qType\fR \fItype\fR -.br - DNS type to query (eg.\& A, NS) -.PP -\fB--no-truncate\fR, \fB+ignore\fR -.br - Ignore UDP truncation (by default, awl \fIretries with TCP\fR) -.PP -\fB-t\fR, \fB--tcp\fR, \fB+tcp\fR, \fB+vc\fR -.br - Use TCP for the query (see \fIRFC 7766\fR) -.PP -\fB--dnscrypt\fR -.br - Use DNSCrypt -.PP -\fB-T\fR, \fB--tls\fR, \fB+tls\fR -.br - Use DNS-over-TLS, implies \fB--tcp\fR (see \fIRFC 7858\fR) -.PP -\fB-H\fR.\& \fB--https\fR, \fB+https\fR -.br - Use DNS-over-HTTPS (see \fIRFC 8484\fR) -.PP -\fB-Q\fR.\& \fB--quic\fR, \fB+quic\fR -.br - Use DNS-over-QUIC (see \fIRFC 9250\fR) -.PP -\fB-x\fR, \fB--reverse\fR -.br - Do a reverse lookup.\& Sets default \fItype\fR to PTR.\& -.br - \fBawl\fR automatically makes an IP or phone number canonical.\& -.PP -.RE -.SS DNS Flags -.PP -.RS 4 -\fB--aa=[false]\fR, \fB+[no]aaflag\fR -.br - (Set, Unset) AA (Authoritative Answer) flag -.PP -\fB--ad=[false]\fR, \fB+[no]adflag\fR -.br - (Set, Unset) AD (Authenticated Data) flag -.PP -\fB--tc=[false]\fR, \fB+[no]tcflag\fR -.br - (Set, Unset) TC (TrunCated) flag -.PP -\fB-z=[false]\fR, \fB+[no]zflag\fR -.br - (Set, Unset) Z (Zero) flag -.PP -\fB--cd=[false]\fR, \fB+[no]cdflag\fR -.br - (Set, Unset) CD (Checking Disabled) flag -.PP -\fB--qr=[false]\fR, \fB+[no]qrflag\fR -.br - (Set, Unset) QR (QueRy) flag -.PP -\fB--rd=[true]\fR, \fB+[no]rdflag\fR -.br - (Set, Unset) RD (Recursion Desired) flag -.PP -\fB--ra=[false]\fR, \fB+[no]raflag\fR -.br - (Set, Unset) RA (Recursion Available) flag -.PP -.RE -.SS Output Display -.RS 4 -\fB--no-question\fR, \fB+noquestion\fR -.br - Do not display the Question section -.PP -\fB--no-answer\fR, \fB+noanswer\fR -.br - Do not display the Answer section -.PP -\fB--no-answer\fR, \fB+noanswer\fR -.br - Do not display the Answer section -.PP -\fB--no-authority\fR, \fB+noauthority\fR -.br - Do not display the Authority section -.PP -\fB--no-additional\fR, \fB+noadditional\fR -.br - Do not display the Additional section -.PP -\fB--no-statistics\fR, \fB+nostatistics\fR -.br - Do not display the Statistics (additional comments) section -.PP -.RE -.SS Output Formats -.RS 4 -\fB-j\fR, \fB--json\fR, \fB+json\fR -.br - Print the query results as JSON.\& -.PP -\fB-X\fR, \fB--xml\fR, \fB+xml\fR -.br - Print the query results as XML.\& -.PP -\fB-y\fR, \fB--yaml\fR, \fB+yaml\fR -.br - Print the query results as YAML.\& -.PP -\fB-s\fR, \fB--short\fR, \fB+short\fR -.br - Print just the results.\& -.PP -.RE -.SH EXAMPLES -.nf -.RS 4 -awl grumbulon\&.xyz -j +cd -.fi -.RE -Run a query of your local resolver for the A records of grumbulon.\&xyz, print -them as JSON and disable DNSSEC verification.\& -.PP -.nf -.RS 4 -awl +short example\&.com AAAA @1\&.1\&.1\&.1 -.fi -.RE -Query 1.\&1.\&1.\&1 for the AAAA records of example.\&com, print just the answers -.PP -.nf -.RS 4 -awl -xT PTR 8\&.8\&.4\&.4 @dns\&.google -.fi -.RE -Query dns.\&google over TLS for the PTR record to the IP address 8.\&8.\&4.\&4 -.PP -.SH SEE ALSO +.\" Generated by scdoc 1.11.2 +.\" Complete documentation for this program is not available as a GNU info page +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.nh +.ad l +.\" Begin generated content: +.TH "awl" "1" "2022-07-24" +.P +.SH NAME +awl - DNS lookup tool +.P +.SH SYNOPSIS +\fBawl\fR [ \fIOPTIONS\fR ] \fIname\fR [ \fI@server\fR ] [ \fItype\fR ] +.br +where +.P +\fIname\fR is the query to make (\fBexample: froth.\&zone\fR) +.br +\fI@server\fR is the server to query (\fBexample: dns.\&froth.\&zone\fR) +.br +\fItype\fR is the DNS resource type (\fBexample: AAAA\fR) +.P +.SH DESCRIPTION +.P +\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 tannery (leatherworking).\& +.P +\fBawl\fR is designed to be a more "modern" version of \fIdrill\fR(1) by including +some more recent RFCs and output options.\& \fBawl\fR is still heavily +Work-In-Progress so some features may get added or removed.\& +.P +.SH OPTIONS +.RS 4 +Dig-like +[no]flags are supported, see dig(1) +.P +\fB-D\fR, \fB--dnssec\fR, \fB+dnssec\fR +.br + Enable DNSSEC.\& This needs to be manually enabled.\& +.P +\fB-v\fR \fIvalue\fR +.br + Set verbosity (currently WIP) +.P +\fB-V\fR +.br + Print the version and exit.\& +.P +\fB-h\fR +.br + Show a "short" help message.\& +.P +.RE +.SS Query Options +.RS 4 +\fB-4\fR +.br + Only make query over IPv4 +.P +\fB-6\fR +.br + Only make query over IPv6 +.P +\fB-p\fR, \fB--port\fR \fIport\fR +.br + Sets the port to query.\& +.br + +.br +\fIDefault Ports\fR: +.RS 4 +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.IP \(bu 4 +.\} +\fI53\fR for \fBUDP\fR and \fBTCP\fR +.RE +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.IP \(bu 4 +.\} +\fI853\fR for \fBTLS\fR and \fBQUIC\fR +.RE +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.IP \(bu 4 +.\} +\fI443\fR for \fBHTTPS\fR + +.RE +.P +.RE +\fB-q\fR, \fB--query\fR \fIdomain\fR +.br + Domain to query (eg.\& example.\&com) +.P +\fB-c\fR, \fB--class\fR \fIclass\fR +.br + DNS class to query (eg.\& IN, CH) +.P +\fB-t\fR, \fB--qType\fR \fItype\fR +.br + DNS type to query (eg.\& A, NS) +.P +\fB--no-truncate\fR, \fB+ignore\fR +.br + Ignore UDP truncation (by default, awl \fIretries with TCP\fR) +.P +\fB--tcp\fR, \fB+tcp\fR, \fB+vc\fR +.br + Use TCP for the query (see \fIRFC 7766\fR) +.P +\fB--dnscrypt\fR, \fB+dnscrypt\fR +.br + Use DNSCrypt +.P +\fB-T\fR, \fB--tls\fR, \fB+tls\fR +.br + Use DNS-over-TLS, implies \fB--tcp\fR (see \fIRFC 7858\fR) +.P +\fB-H\fR.\& \fB--https\fR, \fB+https\fR +.br + Use DNS-over-HTTPS (see \fIRFC 8484\fR) +.P +\fB-Q\fR.\& \fB--quic\fR, \fB+quic\fR +.br + Use DNS-over-QUIC (see \fIRFC 9250\fR) +.P +\fB-x\fR, \fB--reverse\fR +.br + Do a reverse lookup.\& Sets default \fItype\fR to PTR.\& +.br + \fBawl\fR automatically makes an IP or phone number canonical.\& +.P +\fB--timeout\fR \fIseconds\fR, \fB+timeout=\fR\fIseconds\fR +.br + Set the timeout period.\& Floating point numbers are accepted.\& +.br + 0.\&5 seconds is the minimum.\& +.P +\fB--retry\fR \fIint\fR, \fB+tries\fR=\fIint\fR, \fB+ retry\fR=\fIint\fR +.br + Set the number of retries.\& +.br + Retry is one more than tries, dig style +.P +.RE +.SS DNS Flags +.P +.RS 4 +\fB--aa=[false]\fR, \fB+[no]aaflag\fR +.br + (Set, Unset) AA (Authoritative Answer) flag +.P +\fB--ad=[false]\fR, \fB+[no]adflag\fR +.br + (Set, Unset) AD (Authenticated Data) flag +.P +\fB--tc=[false]\fR, \fB+[no]tcflag\fR +.br + (Set, Unset) TC (TrunCated) flag +.P +\fB-z=[false]\fR, \fB+[no]zflag\fR +.br + (Set, Unset) Z (Zero) flag +.P +\fB--cd=[false]\fR, \fB+[no]cdflag\fR +.br + (Set, Unset) CD (Checking Disabled) flag +.P +\fB--qr=[false]\fR, \fB+[no]qrflag\fR +.br + (Set, Unset) QR (QueRy) flag +.P +\fB--rd=[true]\fR, \fB+[no]rdflag\fR +.br + (Set, Unset) RD (Recursion Desired) flag +.P +\fB--ra=[false]\fR, \fB+[no]raflag\fR +.br + (Set, Unset) RA (Recursion Available) flag +.P +.RE +.SS Output Display +.RS 4 +\fB--no-question\fR, \fB+noquestion\fR +.br + Do not display the Question section +.P +\fB--no-answer\fR, \fB+noanswer\fR +.br + Do not display the Answer section +.P +\fB--no-answer\fR, \fB+noanswer\fR +.br + Do not display the Answer section +.P +\fB--no-authority\fR, \fB+noauthority\fR +.br + Do not display the Authority section +.P +\fB--no-additional\fR, \fB+noadditional\fR +.br + Do not display the Additional section +.P +\fB--no-statistics\fR, \fB+nostats\fR +.br + Do not display the Statistics (additional comments) section +.P +.RE +.SS Output Formats +.RS 4 +\fB-j\fR, \fB--json\fR, \fB+json\fR +.br + Print the query results as JSON.\& +.P +\fB-X\fR, \fB--xml\fR, \fB+xml\fR +.br + Print the query results as XML.\& +.P +\fB-y\fR, \fB--yaml\fR, \fB+yaml\fR +.br + Print the query results as YAML.\& +.P +\fB-s\fR, \fB--short\fR, \fB+short\fR +.br + Print just the address of the answer.\& +.P +.RE +.SH EXAMPLES +.nf +.RS 4 +awl grumbulon\&.xyz -j +cd +.fi +.RE +Run a query of your local resolver for the A records of grumbulon.\&xyz, print +them as JSON and disable DNSSEC verification.\& +.P +.nf +.RS 4 +awl +short example\&.com AAAA @1\&.1\&.1\&.1 +.fi +.RE +Query 1.\&1.\&1.\&1 for the AAAA records of example.\&com, print just the answers +.P +.nf +.RS 4 +awl -xT PTR 8\&.8\&.4\&.4 @dns\&.google +.fi +.RE +Query dns.\&google over TLS for the PTR record to the IP address 8.\&8.\&4.\&4 +.P +.SH SEE ALSO \fIdrill\fR(1), \fIdig\fR(1), the many DNS RFCs \ No newline at end of file diff --git a/doc/awl.1.md b/doc/awl.1.md index 560ad6c..623fd95 100644 --- a/doc/awl.1.md +++ b/doc/awl.1.md @@ -66,7 +66,7 @@ _Default Ports_: *--tcp*, *+tcp*, *+vc*++ Use TCP for the query (see _RFC 7766_) - *--dnscrypt*++ + *--dnscrypt*, *+dnscrypt*++ Use DNSCrypt *-T*, *--tls*, *+tls*++ @@ -82,8 +82,13 @@ _Default Ports_: Do a reverse lookup. Sets default _type_ to PTR.++ *awl* automatically makes an IP or phone number canonical. - *--timeout* _seconds_ - Set the timeout period. Floating point numbers are accepted. + *--timeout* _seconds_, *+timeout=*_seconds_++ + Set the timeout period. Floating point numbers are accepted.++ + 0.5 seconds is the minimum. + + *--retry* _int_, *+tries*=_int_, *+ retry*=_int_++ + Set the number of retries.++ + Retry is one more than tries, dig style ## DNS Flags diff --git a/internal/helpers/query.go b/internal/helpers/query.go index a63f3d7..ddc0943 100644 --- a/internal/helpers/query.go +++ b/internal/helpers/query.go @@ -21,4 +21,5 @@ type Request struct { Class uint16 `json:"class"` // DNS Class Name string `json:"name"` // The domain name to make a DNS request for Timeout time.Duration // The maximum timeout + Retries int // Number of queries to retry } diff --git a/logawl/logger.go b/logawl/logger.go index ddae454..b703ba2 100644 --- a/logawl/logger.go +++ b/logawl/logger.go @@ -133,12 +133,12 @@ func (l *Logger) Info(v ...any) { l.Println(InfoLevel, v...) } -// Call print directly with Error level -func (l *Logger) Error(v ...any) { +// Call print directly with Warn level +func (l *Logger) Warn(v ...any) { l.Println(WarnLevel, v...) } -// Call print directly with Fatal level -func (l *Logger) Fatal(v ...any) { +// Call print directly with Error level +func (l *Logger) Error(v ...any) { l.Println(ErrLevel, v...) } diff --git a/main.go b/main.go index 923045a..8d9c4e3 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ import ( "time" "git.froth.zone/sam/awl/cli" + "git.froth.zone/sam/awl/internal/helpers" "git.froth.zone/sam/awl/query" "gopkg.in/yaml.v2" @@ -27,12 +28,24 @@ func main() { if errors.Is(err, cli.ErrNotError) || strings.Contains(err.Error(), "help requested") { os.Exit(0) } - opts.Logger.Fatal(err) + opts.Logger.Error(err) os.Exit(1) } - resp, err := query.CreateQuery(opts) + var resp helpers.Response + + // Retry queries if a query fails + for i := 0; i < opts.Request.Retries; i++ { + resp, err = query.CreateQuery(opts) + if err == nil { + break + } else { + opts.Logger.Warn("Retrying") + } + } + + // Query failed, make it fail if err != nil { - opts.Logger.Fatal(err) + opts.Logger.Error(err) os.Exit(9) } switch { @@ -40,7 +53,7 @@ func main() { opts.Logger.Info("Printing as JSON") json, err := json.MarshalIndent(resp.DNS, "", " ") if err != nil { - opts.Logger.Fatal(err) + opts.Logger.Error(err) os.Exit(10) } fmt.Println(string(json)) @@ -48,7 +61,7 @@ func main() { opts.Logger.Info("Printing as XML") xml, err := xml.MarshalIndent(resp.DNS, "", " ") if err != nil { - opts.Logger.Fatal(err) + opts.Logger.Error(err) os.Exit(10) } fmt.Println(string(xml)) @@ -56,7 +69,7 @@ func main() { opts.Logger.Info("Printing as YAML") yaml, err := yaml.Marshal(resp.DNS) if err != nil { - opts.Logger.Fatal(err) + opts.Logger.Error(err) os.Exit(10) } fmt.Println(string(yaml))