Golang中的mDNS问题处理(mDNS系列3)

起因

为了在Windows上跨平台编译Linux上运行的Golang程序,选择了CGO_ENABLED=0。此时DNS解析也被切换至了纯Go版本的解析器上,而这个版本的解析器不支持mDNS。为了方便我在自家使用mDNS,便有了下文的方案探究。

分析

首先查找了一些官方文档,发现官方明确短期内是不会在纯Go版本的解析器上支持mDNS的,具体详见参考1。为了通用,便想到了monkey patch这一方式去处理,而这种在Python很好处理的方案,在Golang中却并不好写。

在我目前写的代码中就gorm.io/gorm(gorm.io/driver/postgres)github.com/influxdata/influxdb-client-go/v2这两部分库会用到mDNS,追了一下源码,发现会公共调用func (r *Resolver) lookupIPAddr(ctx context.Context, network, host string) ([]IPAddr, error)这个方法,那理论上只要处理它即可。之后查了使用较多的hook开源库,却发现gomonkey支持hook私有方法,但是hook后却没有办法调用原方法,而gohook支持调用原方法,却不支持hook私有方法。在衡量了一下处理难度后,决定对代码调用往上追踪一下,直到发现公有方法,之后在使用gohook处理。而这两个方法便是func (r *Resolver) LookupHost(ctx context.Context, host string) (addrs []string, err error) func (d *Dialer) DialContext(ctx context.Context, network, address string) (Conn, error)

处理

完整版代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package main

import (
"context"
"net"
"os/exec"
"strings"

"github.com/brahma-adshonor/gohook"
)

func ResolverLookupHostAddMDNSSupport(r *net.Resolver, ctx context.Context, host string) (addrs []string, err error) {
if addrs, err = ResolverLookupHostTramp(r, ctx, host); err == nil {
return addrs, err
} else if strings.HasSuffix(host, ".local") {
cmd := exec.Command("avahi-resolve-address", "-n", host)
output, err := cmd.Output()
if err != nil {
return nil, err
}
tmp := strings.Split(string(output), "\t")
address := strings.Trim(tmp[len(tmp)-1], "\n")
return []string{address}, nil
} else {
return nil, err
}
}

func ResolverLookupHostTramp(r *net.Resolver, ctx context.Context, host string) (addrs []string, err error) {
return nil, nil
}

func DialContextAddMDNSSupport(d *net.Dialer, ctx context.Context, network, address string) (net.Conn, error) {
if conn, err := DialContextTramp(d, ctx, network, address); err == nil {
return conn, err
} else if strings.Contains(address, ".local") {
host := strings.Split(address, ".local")[0] + ".local"
cmd := exec.Command("avahi-resolve-address", "-n", host)
output, err := cmd.Output()
if err != nil {
return nil, err
}
tmp := strings.Split(string(output), "\t")
address = strings.ReplaceAll(address, host, strings.Trim(tmp[len(tmp)-1], "\n"))
return DialContextTramp(d, ctx, network, address)
} else {
return nil, err
}
}

func DialContextTramp(d *net.Dialer, ctx context.Context, network, address string) (net.Conn, error) {
return nil, nil
}

func AddMDNSSupport() {
var d *net.Dialer
err := gohook.HookMethod(net.DefaultResolver, "LookupHost", ResolverLookupHostAddMDNSSupport, ResolverLookupHostTramp)
if err != nil {
panic(err)
}
err = gohook.HookMethod(d, "DialContext", DialContextAddMDNSSupport, DialContextTramp)
if err != nil {
panic(err)
}
}

之后在进入主流程前调用AddMDNSSupport即可,我个人是把调用放到了init函数当中了。注意使用时必须保证主机上avahi-resolve-address命令执行正常。

参考

  1. net: pure Go resolver(netdns) can not resolve hostname.local
  2. gomonkey
  3. gohook