Skip to content

Commit

Permalink
Continue refactoring (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
picatz authored Aug 25, 2024
1 parent bd3a48d commit f658ac7
Show file tree
Hide file tree
Showing 10 changed files with 828 additions and 99 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/doh
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,28 @@ Using [`cloudflare`](https://developers.cloudflare.com/1.1.1.1/dns-over-https/),
> Since `doh` outputs everything as JSON, it pairs really well with tools like [`jq`](https://stedolan.github.io/jq/) to parse relevant parts of the output for your purposes.
# Install

To get started, you will need [`go`](https://golang.org/doc/install) installed and properly configured.

```shell
$ go install -v github.com/picatz/doh@latest
```

# Help Menus

The `--help` command-line flag can show you the top-level help menu.

```console
$ doh --help
doh is a CLI for querying DNS records from DoH servers

Usage:
doh [command]

Available Commands:
completion Generate the autocompletion script for the specified shell
help Help about any command
query Query domains for DNS records in JSON
query Query DNS records from DoH servers

Flags:
-h, --help help for doh
Expand All @@ -52,6 +58,7 @@ Flags:
-h, --help help for query
--resolver-addr string address of a DNS resolver to use for resolving DoH server names (e.g. 8.8.8.8:53)
--resolver-network string protocol to use for resolving DoH server names (e.g. udp, tcp) (default "udp")
--retry-max int maximum number of retries for each query (default 10)
--servers strings servers to query (default [https://dns.google/dns-query,https://cloudflare-dns.com/dns-query,https://dns.quad9.net:5053/dns-query])
--timeout duration timeout for query, 0s for no timeout (default 30s)
--type string dns record type to query for each domain, such as A, AAAA, MX, etc. (default "A")
Expand Down Expand Up @@ -90,19 +97,22 @@ google.com 172.217.0.174
```

To get `IPv6` records, we'll need to specify the `--type` flag, like so:
```

```console
$ doh query google.com --type AAAA
...
```

To get `MX` records:
```

```console
$ doh query google.com --type MX
...
```

To get `ANY` records (which is only implemented by Google at the moment):
```

```console
$ doh query google.com --type ANY --servers=https://dns.google.com/resolve
...
```
Expand Down
11 changes: 6 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ go 1.21

require (
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/hashicorp/go-retryablehttp v0.7.7
github.com/miekg/dns v1.1.62
github.com/spf13/cobra v1.8.1
golang.org/x/sync v0.7.0
golang.org/x/sync v0.8.0
)

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/tools v0.22.0 // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/tools v0.24.0 // indirect
)
20 changes: 20 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
Expand All @@ -12,13 +22,23 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
38 changes: 37 additions & 1 deletion internal/cli/command_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"time"

"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/go-retryablehttp"
"github.com/picatz/doh/pkg/dj"
"github.com/picatz/doh/pkg/doh"
"github.com/spf13/cobra"
Expand All @@ -21,6 +22,32 @@ type result struct {
Resp *dj.Response `json:"resp"`
}

func newClient(retryMax int) (*http.Client, error) {
retryClient := retryablehttp.NewClient()

retryClient.RetryMax = retryMax

retryClient.HTTPClient = cleanhttp.DefaultClient()

retryClient.Logger = nil // TODO: consider logger

retryClient.CheckRetry = func(ctx context.Context, resp *http.Response, err error) (bool, error) {
return retryablehttp.DefaultRetryPolicy(ctx, resp, err)
}

retryClient.Backoff = func(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
return retryablehttp.DefaultBackoff(min, max, attemptNum, resp)
}

retryClient.ErrorHandler = func(resp *http.Response, err error, numTries int) (*http.Response, error) {
return nil, err
}

httpClient := retryClient.StandardClient()

return httpClient, nil
}

var CommandQuery = &cobra.Command{
Use: "query domains... [flags]",
Short: "Query DNS records from DoH servers",
Expand All @@ -44,7 +71,15 @@ which can be piped to other commands (e.g. jq) or redirected to a file.`,
return fmt.Errorf("invalid timeout: %w", err)
}

httpClient := cleanhttp.DefaultClient()
retryMax, err := cmd.Flags().GetInt("retry-max")
if err != nil {
return fmt.Errorf("invalid retry max: %w", err)
}

httpClient, err := newClient(retryMax)
if err != nil {
return fmt.Errorf("error creating http client: %w", err)
}

resolverAddr, err := cmd.Flags().GetString("resolver-addr")
if err != nil {
Expand Down Expand Up @@ -136,6 +171,7 @@ func init() {
CommandQuery.Flags().Duration("timeout", 30*time.Second, "timeout for query, 0s for no timeout")
CommandQuery.Flags().String("resolver-addr", "", "address of a DNS resolver to use for resolving DoH server names (e.g. 8.8.8.8:53)")
CommandQuery.Flags().String("resolver-network", "udp", "protocol to use for resolving DoH server names (e.g. udp, tcp)")
CommandQuery.Flags().Int("retry-max", 10, "maximum number of retries for each query")

CommandRoot.AddCommand(CommandQuery)
}
89 changes: 89 additions & 0 deletions internal/cli/command_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package cli_test

import (
"bytes"
"io"
"testing"

"github.com/picatz/doh/internal/cli"
)

func testCommand(t *testing.T, args ...string) io.Reader {
t.Helper()

cli.CommandRoot.SetArgs(args)

output := bytes.NewBuffer(nil)

cli.CommandRoot.SetOut(output)

err := cli.CommandRoot.Execute()
if err != nil {
t.Fatal(err)
}

return output
}

func TestCommand(t *testing.T) {
tests := []struct {
name string
args []string
check func(t *testing.T, output io.Reader)
}{
{
name: "help",
args: []string{"--help"},
check: func(t *testing.T, output io.Reader) {
b, err := io.ReadAll(output)
if err != nil {
t.Fatal(err)
}

if len(b) == 0 {
t.Error("got no help output")
}
},
},
{
name: "google.com",
args: []string{"query", "google.com"},
check: func(t *testing.T, output io.Reader) {
b, err := io.ReadAll(output)
if err != nil {
t.Fatal(err)
}

if len(b) == 0 {
t.Fatal("got no output for known domain")
}

t.Log(string(b))
},
},
{
name: "cloudflare.com",
args: []string{"query", "cloudflare.com"},
check: func(t *testing.T, output io.Reader) {
b, err := io.ReadAll(output)
if err != nil {
t.Fatal(err)
}

if len(b) == 0 {
t.Fatal("got no output for known domain")
}

t.Log(string(b))
},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
output := testCommand(t, test.args...)

test.check(t, output)
})
}
}
Loading

0 comments on commit f658ac7

Please sign in to comment.