func ChainResolvers(primary, secondary modelx.DNSResolver) modelx.DNSResolver {
	return resolver.ChainResolver{
		Primary:   chainWrapperResolver{DNSResolver: primary},
		Secondary: chainWrapperResolver{DNSResolver: secondary},
	}
}
ChainResolvers chains a primary and a secondary resolver such that
we can fallback to the secondary if primary is broken.
package netx
import (
	"context"
	"errors"
	"net"
	"net/http"
	"strings"
	"sync"
	"time"
	"github.com/ooni/probe-engine/legacy/netx/handlers"
	"github.com/ooni/probe-engine/legacy/netx/modelx"
	"github.com/ooni/probe-engine/netx/resolver"
)
var (
	dohClientHandle *http.Client
	dohClientOnce   sync.Once
)
func newHTTPClientForDoH(beginning time.Time, handler modelx.Handler) *http.Client {
	if handler == handlers.NoHandler {
		dohClientOnce.Do(func() {
			transport := newHTTPTransport(
				time.Now(),
				handlers.NoHandler,
				newDialer(time.Now(), handler),
				false,  
				http.ProxyFromEnvironment,
			)
			dohClientHandle = &http.Client{Transport: transport}
		})
		return dohClientHandle
	}
	transport := newHTTPTransport(
		beginning,
		handler,
		newDialer(beginning, handler),
		true,  
		http.ProxyFromEnvironment,
	)
	return &http.Client{Transport: transport}
}
func withPort(address, port string) string {
	_, _, err := net.SplitHostPort(address)
	if err != nil && strings.Contains(err.Error(), "missing port in address") {
		address = net.JoinHostPort(address, port)
	}
	return address
}
type resolverWrapper struct {
	beginning time.Time
	handler   modelx.Handler
	resolver  modelx.DNSResolver
}
func newResolverWrapper(
	beginning time.Time, handler modelx.Handler,
	resolver modelx.DNSResolver,
) *resolverWrapper {
	return &resolverWrapper{
		beginning: beginning,
		handler:   handler,
		resolver:  resolver,
	}
}
func (r *resolverWrapper) LookupHost(ctx context.Context, hostname string) ([]string, error) {
	ctx = maybeWithMeasurementRoot(ctx, r.beginning, r.handler)
	return r.resolver.LookupHost(ctx, hostname)
}
func newResolver(
	beginning time.Time, handler modelx.Handler, network, address string,
) (modelx.DNSResolver, error) {
	if network == "system" || network == "" {
		return newResolverWrapper(
			beginning, handler, newResolverSystem()), nil
	}
	if network == "doh" {
		return newResolverWrapper(beginning, handler, newResolverHTTPS(
			newHTTPClientForDoH(beginning, handler), address,
		)), nil
	}
	if network == "dot" {
		return newResolverWrapper(beginning, handler, newResolverTLS(
			newDialer(beginning, handler).DialTLSContext, withPort(address, "853"),
		)), nil
	}
	if network == "tcp" {
		return newResolverWrapper(beginning, handler, newResolverTCP(
			newDialer(beginning, handler).DialContext, withPort(address, "53"),
		)), nil
	}
	if network == "udp" {
		return newResolverWrapper(beginning, handler, newResolverUDP(
			newDialer(beginning, handler), withPort(address, "53"),
		)), nil
	}
	return nil, errors.New("resolver.New: unsupported network value")
}
func NewResolver(network, address string) (modelx.DNSResolver, error) {
	return newResolver(time.Now(), handlers.NoHandler, network, address)
}
type chainWrapperResolver struct {
	modelx.DNSResolver
}
func (r chainWrapperResolver) Network() string {
	return "chain"
}
func (r chainWrapperResolver) Address() string {
	return ""
}