Golang中自定义DNS解析器无法正确返回错误的问题

Golang中自定义DNS解析器无法正确返回错误的问题 目前,Golang的标准包(即net)允许我们自定义DNS解析器,以针对特定的DNS服务器进行DNS查询,如Dial字段中所述

type Resolver struct {
        // PreferGo控制在Go内置DNS解析器可用的平台上是否优先使用它。
        // 它等同于设置GODEBUG=netdns=go,但仅适用于此解析器。
        PreferGo bool

        // StrictErrors控制在使用Go内置解析器时临时错误(包括超时、套接字错误和SERVFAIL)的行为。
        // 对于由多个子查询组成的查询(例如A+AAAA地址查找或遍历DNS搜索列表),此选项会导致此类错误中止整个查询,而不是返回部分结果。
        // 默认情况下未启用此选项,因为它可能会影响与处理AAAA查询不正确的解析器的兼容性。
        StrictErrors bool

        // Dial可选地指定一个替代的拨号器,供Go内置DNS解析器用于建立到DNS服务的TCP和UDP连接。
        // 地址参数中的主机将始终是字面IP地址而不是主机名,端口参数中的端口将是字面端口号而不是服务名称。
        // 如果返回的Conn也是PacketConn,则发送和接收的DNS消息必须符合RFC 1035第4.2.1节“UDP使用”。
        // 否则,通过Conn传输的DNS消息必须符合RFC 7766第5节“传输协议选择”。
        // 如果为nil,则使用默认拨号器。
        Dial func(ctx context.Context, network, address string) (Conn, error)
        // 包含已过滤或未导出的字段
}

然而,由于此解决方案修改了非常底层的函数,所有上层调用链并不知道自定义DNS服务器的地址。如果我们使用它查询一个DNS A记录,并且该记录本不应被找到,上层函数返回的错误信息可能会产生误导。

以下是一个简要示例:

package main

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

func main() {
	customR := net.Resolver{
		PreferGo: true,
		Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
			d := net.Dialer{}
			return d.DialContext(ctx, "udp", "8.8.8.8:53")
		},
	}
	ips, err := customR.LookupHost(context.Background(), "jaslkdjlsajdla.google.com")

	fmt.Println("ips:", ips)
	fmt.Println("err:", err)
}

上述代码将返回:ips: []err: lookup jaslkdjlsajdla.google.com on 8.8.4.4:53: no such host,而不是err: lookup jaslkdjlsajdla.google.com on 8.8.8.8:53: no such host。实际上,8.8.4.4取决于本地环境中的/etc/resolv.conf

有没有更好的解决方案可以让上述代码返回正确的错误而不是这个误导性的错误?

谢谢!


更多关于Golang中自定义DNS解析器无法正确返回错误的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

我不建议这样做,但我要指出这并非修改解析器的理想方式。原因之一就是你刚刚发现的问题。

更多关于Golang中自定义DNS解析器无法正确返回错误的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Golang中,当使用自定义DNS解析器时,确实会遇到错误信息中显示错误DNS服务器地址的问题。这是因为Go的DNS错误处理机制在构建错误消息时,默认使用系统配置的DNS服务器地址,而不是自定义解析器中实际使用的地址。

要解决这个问题,可以通过实现自定义的错误处理来包装原始错误,提供准确的DNS服务器信息。以下是改进后的代码示例:

package main

import (
	"context"
	"errors"
	"fmt"
	"net"
	"strings"
)

type CustomResolver struct {
	resolver *net.Resolver
	server   string
}

func NewCustomResolver(dnsServer string) *CustomResolver {
	return &CustomResolver{
		resolver: &net.Resolver{
			PreferGo: true,
			Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
				d := net.Dialer{}
				return d.DialContext(ctx, network, dnsServer)
			},
		},
		server: dnsServer,
	}
}

func (cr *CustomResolver) LookupHost(ctx context.Context, host string) ([]string, error) {
	ips, err := cr.resolver.LookupHost(ctx, host)
	if err != nil {
		// 包装错误以包含实际的DNS服务器地址
		if strings.Contains(err.Error(), "no such host") {
			return nil, fmt.Errorf("lookup %s on %s: no such host", host, cr.server)
		}
		// 处理其他类型的DNS错误
		return nil, fmt.Errorf("lookup %s on %s: %w", host, cr.server, err)
	}
	return ips, nil
}

func main() {
	customR := NewCustomResolver("8.8.8.8:53")
	ips, err := customR.LookupHost(context.Background(), "jaslkdjlsajdla.google.com")

	fmt.Println("ips:", ips)
	fmt.Println("err:", err)
}

这段代码创建了一个CustomResolver结构体,它包装了标准的net.Resolver,但在返回错误时使用实际的DNS服务器地址。当查询不存在的域名时,现在会显示正确的错误信息:lookup jaslkdjlsajdla.google.com on 8.8.8.8:53: no such host

对于其他DNS操作(如LookupIPLookupAddr等),可以类似地实现相应的方法:

func (cr *CustomResolver) LookupIP(ctx context.Context, network, host string) ([]net.IP, error) {
	ips, err := cr.resolver.LookupIP(ctx, network, host)
	if err != nil {
		return nil, fmt.Errorf("lookup %s on %s: %w", host, cr.server, err)
	}
	return ips, nil
}

func (cr *CustomResolver) LookupAddr(ctx context.Context, addr string) ([]string, error) {
	names, err := cr.resolver.LookupAddr(ctx, addr)
	if err != nil {
		return nil, fmt.Errorf("lookup %s on %s: %w", addr, cr.server, err)
	}
	return names, nil
}

这种方法确保了错误信息中始终包含实际用于查询的DNS服务器地址,避免了误导性的错误报告。

回到顶部