Skip to content

net.Resolver Ignores Custom Dialer #33086

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
moloch-- opened this issue Jul 12, 2019 · 2 comments
Closed

net.Resolver Ignores Custom Dialer #33086

moloch-- opened this issue Jul 12, 2019 · 2 comments

Comments

@moloch--
Copy link

moloch-- commented Jul 12, 2019

What version of Go are you using (go version)?

$ go version
go version go1.12.7 darwin/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/moloch/Library/Caches/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/moloch/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/opt/go/libexec"
GOTMPDIR=""
GOTOOLDIR="/usr/local/opt/go/libexec/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/n3/b920qr2s0wj9h9z0hyj678vr0000gn/T/go-build549888698=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

Attempting to cross-compile a Windows binary from MacOS that does DNS-over-TCP, to do this I implemented a custom dialer that uses TCP (code excerpt is below). The dialer is ignored and the program still performs DNS resolution over UDP (verified with Wireshark), and does not return any errors. Setting preferGo to true or false does not appear to have any affect. Note that while I'm compiling on my Mac (version/env info above) the code is running on Windows.

func dnsTCPLookup(resolverIP string, domain string) (string, error) {
	tcpResolver := net.Resolver{
		PreferGo: true,
		Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
			log.Printf("CUSTOM DIALER CALLED\n")
			dialer := net.Dialer{}
			return dialer.DialContext(ctx, "tcp", fmt.Sprintf("%s:53", resolverIP))
		},
	}
	log.Printf("[dns] lookup -> %s", domain)
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	txts, err := tcpResolver.LookupTXT(ctx, domain)
	if err != nil || len(txts) == 0 {
		log.Printf("[!] failure -> %s", domain)
		return "", err
	}
	return strings.Join(txts, ""), nil
}

CUSTOM DIALER CALLED is not log'd.

What did you expect to see?

Go attempt to establish a connection using the provided TCP Dialer, log.Printf statement, or an error.

What did you see instead?

DNS resolution over UDP, no errors.

@fraenkel
Copy link
Contributor

You might have missed these comments in the net documentation:

 // PreferGo controls whether Go's built-in DNS resolver is preferred
 // on platforms where it's available.

and more importantly

On Windows, the resolver always uses C library functions, such as GetAddrInfo and DnsQuery.

@moloch--
Copy link
Author

moloch-- commented Jul 13, 2019

Ah okay, it's strange because nslookup will fallback to TCP-over-DNS but the C functions that Go attempts to use just fail, which is why I was attempting to use a custom dialer. Is there anyway to do DNS-over-TCP in Go?

This seems to be a duplicate of 29621

jsha pushed a commit to letsencrypt/pebble that referenced this issue Jul 29, 2019
Pebble accepts a `--dnsserver` flag, that allows to select a custom
DNS resolver to use when validating the ACME challenges. This option
is critical to make the certbot integration tests work in particular.

Current implementation is to set the `net.DefaultResolver` on a new
`net.Resolver` instance with a custom `Dial` property to consume the
value from `--dnsserver`. This allows to transparently resolve any
host using this custom DNS resolver from a pure-Go implementation of
the DNS resolution API.

Sadly this approach does not work on Windows, and the `--dnsserver`
has no effect on how resolution is done, and it is always the OS
mechanism that is used.

One can reveal this behavior by running the following piece of code on
Linux and on Windows:

```go
// test.go
package main

import (
	"context"
	"fmt"
	"net"
)

func main() {
	resolver := &net.Resolver{
		PreferGo: true,
		Dial: func(ctx context.Context, _, _ string) (net.Conn, error) {
			d := net.Dialer{}
			return d.DialContext(ctx, "udp", "4.3.2.1:404")
		},
	}
	net.DefaultResolver = resolver
	ctx, cancelfunc := context.WithTimeout(context.Background(), 30)
	defer cancelfunc()
	_, err := resolver.LookupTXT(ctx, "stupid.entry.net")

	fmt.Printf("Error is: %s\n", err)
}
```

This piece of code tries to resolve a non-existent domain on a
non-existent DNS server as IP `4.3.2.1:404`.

On Linux, you will get the following error:

```bash
Error is: lookup stupid.entry.net on 127.0.0.53:53: dial udp 4.3.2.1:404: i/o timeout
```

That indicates that the system tried to reach the non-existent DNS server,
and get back a timeout exception on it.

However on Windows you will get:
```bash
Error is: lookup stupid.entry.net: dnsquery: DNS name does not exist.
```

This indicates that the system ignored the dummy DNS server address,
contacted the OS DNS resolver, that responded that the DNS name does
not exist.

One can see also the reason for this behavior on Windows on the `net`
godoc, https://godoc.org/net, in particular this line in the module
introduction:

```
On Windows, the resolver always uses C library functions, such as GetAddrInfo and DnsQuery.
```

In fact, the pure Go DNS resolver is not applicable on Windows, the OS
DNS resolver will be used, ignoring any customization.

Several relevant discussions, in particular a proposal (not developed yet)
to make the pure Go DNS resolver available on Windows:

* golang/go#29621
* golang/go#33097
* golang/go#33086

To fix this, this PR makes Pebble switch to a different logic:
* if `-dnsserver` is not set, use the default API to resolve the names
* if `-dnsserver` is set, use a dedicated DNS client, to avoid to use
the OS one both on Linux and Windows

The DNS client is https://github.com/miekg/dns, a highly used and
supported DNS library.

With these modifications, integrations tests on Certbot are running
correctly both on Linux and Windows.
@golang golang locked and limited conversation to collaborators Jul 12, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants