Skip to content

net: allow custom Resolver method implementation(s) #12503

Open
@sajal

Description

@sajal

I mentioned in #12476 that I would like to detect the time it took for DNS resolution phase of the Dial process in Dialer.Dial . The solution posted there was very hacky and adds unnecessarily to the API.

@bradfitz suggested

Perhaps net.Dialer could have an optional Resolver, akin to how http.Client has an optional Transport, or http.Server has an optional ErrorLog, etc.

This seems like an excellent idea. Here is how I propose we go about it by adding minimal complexity and preserving code compatibility.

I propose net package adds a new Resolver interface.

type Resolver interface {
    Resolve(host string, deadline time.Time) (addrs []IPAddr, err error)
}

The signature of Resolver.Resolve is same as lookupIPDeadline which Dial eventually uses. Dialer gets an optional field CustomResolver of type Resolver.

The Resolver object (or nil) gets passed around thru the resolution process.

Dialer.Dial -> resolveAddrList -> internetAddrList .

internetAddrList currently always uses lookupIPDeadline, it would need to be changed such that if the passed custom resolver is not nil then use it, otherwise use lookupIPDeadline.

Other functions calling resolveAddrList or internetAddrList would need to be modified to add an extra nil argument . This does not break code compatibility because they are unexported functions.

Benefits of allowing a custom Resolver

Activity

changed the title [-]proposal: Allow passing of custom Resolver to Dialer.dial in package net[/-] [+]proposal: Allow passing of custom Resolver to Dialer.Dial in package net[/+] on Sep 4, 2015
changed the title [-]proposal: Allow passing of custom Resolver to Dialer.Dial in package net[/-] [+]proposal: Allow passing of custom Resolver to net.Dialer[/+] on Sep 4, 2015
added this to the Unplanned milestone on Sep 4, 2015
sajal

sajal commented on Sep 9, 2015

@sajal
Author

Should I implement and submit the change for codereview? Or wait for some comments here?

bradfitz

bradfitz commented on Sep 9, 2015

@bradfitz
Contributor

No need to prototype it yet. The code will be relatively easy compared to getting the design right.

I suspect that signature isn't general enough. Maybe it's good enough for a dialer, but perhaps it needs a different name.

I bet we don't want to define an interface in the net package. If anything, it could just be an optional func type on the Dialer, similar to funcs on http://golang.org/pkg/net/http/#Transport

sajal

sajal commented on Sep 9, 2015

@sajal
Author

Perhaps call it Lookupfunc(or better name) and deadline-ing is handled inside net package. It might mirror signature of net.LookupIP which is used by default if Lookupfunc is nil.

Anything that does a lookup could ask for optional field for Lookupfunc to allow user to provide their own implementation.

modified the milestones: Proposal, Unplanned on Oct 24, 2015
changed the title [-]proposal: Allow passing of custom Resolver to net.Dialer[/-] [+]proposal: allow net.Dialer to use custom resolver[/+] on Oct 24, 2015
changed the title [-]proposal: allow net.Dialer to use custom resolver[/-] [+]proposal: net: allow Dialer to use custom resolver[/+] on Oct 24, 2015
benburkert

benburkert commented on Nov 5, 2015

@benburkert
Contributor

I would also like to see a Resolver interface but with multiple methods that match the net.Lookup* funcs.

type Resolver interface {
  LookupAddr(addr string) (names []string, err error)
  LookupCNAME(name string) (cname string, err error)
  LookupHost(host string) (addrs []string, err error)
  LookupIP(host string) (ips []IP, err error)
  LookupMX(name string) (mxs []*MX, err error)
  LookupNS(name string) (nss []*NS, err error)
  LookupPort(network, service string) (port int, err error)
  LookupSRV(service, proto, name string) (cname string, addrs []*SRV, err error)
  LookupTXT(name string) (txts []string, err error)
}

The timeout & deadline functionality could be configured when the resolver is created:

func NewResolver(options ResolverOption...) (Resolver, error)

type ResolverOption func(*resolver) error

func ResolverTimeout(duration time.Duration) ResolverOption
func ResolverDeadline(deadline time.Time) ResolverOption

59 remaining items

cevatbarisyilmaz

cevatbarisyilmaz commented on Nov 6, 2019

@cevatbarisyilmaz

I pushed the commit though I didn't really test it yet (at least it didn't disrupt the current tests). Feel free to open an issue in the repo for any further things.

jmalloc

jmalloc commented on Nov 6, 2019

@jmalloc
Contributor

Is there any chance that whatever approach is taken here (in the net package I mean) could/would allow for building a resolver that supports multicast DNS (even if it's only legacy queries)? It seems that native Go implementation does not support mDNS, and hence it is unavailable when building with CGO disabled.

ncruces

ncruces commented on May 29, 2020

@ncruces
Contributor

See github.com/ncruces/go-dns for more "prior art" on hooking into net.Resolver.Dial. Implemented caching, opportunistic encryption and DoH/DoS.

This bit implements the strategy mentioned in the above comment (a fake net.Conn that you can return from Dial). With this in place, implementing the DoH exchange is a relatively simple matter.

codeskyblue

codeskyblue commented on May 13, 2021

@codeskyblue

Check out https://godoc.org/github.com/benburkert/dns for some prior art of that glue package.

I figured out the code bellow.

package main

import (
	"log"
	"net"
	"time"

	"github.com/benburkert/dns"
)

func init() {
	zone := &dns.Zone{
		Origin: "example.org.",
		TTL:    5 * time.Minute,
		RRs: dns.RRSet{
			"foo": {
				dns.TypeA: []dns.Record{
					&dns.A{A: net.ParseIP("1.2.3.4")},
				},
			},
		},
	}
	mux := new(dns.ResolveMux)
	mux.Handle(dns.TypeANY, zone.Origin, zone)

	net.DefaultResolver = &net.Resolver{
		PreferGo: true,
		Dial: (&dns.Client{
			Resolver: mux,
		}).Dial,
	}
}

func main() {
	log.Println(net.LookupHost("foo.example.org")) // it's working. output: [1.2.3.4] <nil>
	log.Println(net.LookupHost("www.example.org")) // error no such host. I don't known how to fallback to DNS query
}
aojea

aojea commented on Sep 7, 2021

@aojea
Contributor

Copying my comment from https://go-review.googlesource.com/c/go/+/115855#message-d80f076d91f28a2e5aa2f1eb6fdd88a33aec9502 ....

I can't think of a name or pattern I like here. The closest is naming them all LookupFooFunc, but then there's too many of them.
I do want this behavior, but we kinda already have the hook we need for testing purposes: Resolver.Dial.
We just need an easy way to wire up high-level Lookup func literals into fake in-memory DNS-speaking func implementations to assign to the Resolver.Dial field.
Before we add a bunch of stuff, I'd like to see a package (we could even put it in golang.org/x/net or nettest) that does the in-memory fake-DNS stuff and answers the DNS queries using the test/etc-provided LookupFoo funcs. Such a package should be even easier lately given the recent work on golang.org/x/net/dns.
(The user would probably also have to set Resolver.PreferGo to true to force the Dial to be used instead of cgo?)
So let's put this on hold for now until we see what such a package might look & feel like.

/cc @iangudger

@bradfitz check if this similar to what you had in mind https://go-review.googlesource.com/c/net/+/347850

ns-jisorce

ns-jisorce commented on Dec 7, 2021

@ns-jisorce

Hi @aojea Hi @bradfitz , any update on this ? Thx

TyeMcQueen

TyeMcQueen commented on Nov 16, 2022

@TyeMcQueen

I've prototyped a fairly simple change that significantly narrows the required customization to a single-function interface that doesn't abstract everything that Resolver does, just the part that is used by net.Dialer (and thus by net/http), namely the internetAddrList() private method.

In the net package, I add (in dial.go in my prototype):

type InternetAddrLister interface {
    InternetAddrList(ctx context.Context, net, addr string) ([]Addr, error)
}

And allow users to provide an alternate implementation for net.Dialer to use.

For maximum usefulness, it is best to also allow a *net.Resolver to be used as an InternetAddrLister so people can provide a replacement by just wrapping around the existing net.Resolver implementation (for cases where they want to do light customization rather than provide a whole custom resolution implementation).

I think this function is a very useful abstraction (and the Go authors appear to agree since this is used quite a few places) and we should just make it a public method of Resolver.

I can submit my changes but I wanted to align on the approach before doing so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @benburkert@bradfitz@mauricio@anatol@davecheney

        Issue actions

          net: allow custom Resolver method implementation(s) · Issue #12503 · golang/go