· code
Golang自定义DNS Nameserver
某些情况下我们希望程序通过自定义Nameserver去查询域名,而不希望通过操作系统给定的Nameserver,本文介绍如何在Golang中实现自定义Nameserver。
DNS 解析过程
Golang中一般通过net.Resolver的LookupHost(ctx context.Context, host string) (addrs []string, err error)去实现域名解析,解析过程如下:
- 检查本地
hosts文件是否存在解析记录,存在即返回解析地址 - 不存在即根据
resolv.conf中读取的nameserver发起递归查询 nameserver不断的向上级nameserver发起迭代查询nameserver最终返回查询结果给请求者
用户可以通过修改/etc/resolv.conf来添加特定的nameserver,但某些场景下我们不希望更改系统配置。比如在kubernetes中,作为sidecar服务需要通过service去访问其他集群内服务,必须更改dnsPolicy为ClusterFirst,但这可能会影响其他容器的 DNS 查询效率。
自定义 Nameserver
在Golang中自定义Nameserver,需要我们自己实现一个Resolver,如果是httpClient需要自定义DialContext()
Resolver实现如下:
// 默认dialer
dialer := &net.Dialer{
Timeout: 1 * time.Second,
}
// 定义resolver
resolver := &net.Resolver{
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
return dialer.DialContext(ctx, "tcp", nameserver) // 通过tcp请求nameserver解析域名
},
}
自定义Dialer如下:
type Dialer struct {
dialer *net.Dialer
resolver *net.Resolver
nameserver string
}
// NewDialer create a Dialer with user's nameserver.
func NewDialer(dialer *net.Dialer, nameserver string) (*Dialer, error) {
conn, err := dialer.Dial("tcp", nameserver)
if err != nil {
return nil, err
}
defer conn.Close()
return &Dialer{
dialer: dialer,
resolver: &net.Resolver{
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
return dialer.DialContext(ctx, "tcp", nameserver)
},
},
nameserver: nameserver, // 用户设置的nameserver
}, nil
}
// DialContext connects to the address on the named network using
// the provided context.
func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
ips, err := d.resolver.LookupHost(ctx, host) // 通过自定义nameserver查询域名
for _, ip := range ips {
// 创建链接
conn, err := d.dialer.DialContext(ctx, network, ip+":"+port)
if err == nil {
return conn, nil
}
}
return d.dialer.DialContext(ctx, network, address)
}
httpClient中自定义DialContext()如下:
ndialer, _ := NewDialer(dialer, nameserver)
client := &http.Client{
Transport: &http.Transport{
DialContext: ndialer.DialContext,
TLSHandshakeTimeout: 10 * time.Second,
},
Timeout: timeout,
}
总结
通过以上实现可解决自定义Nameserver,也可以在Dailer中添加缓存,实现 DNS 缓存。