需要对某手机 app 做分析和诊断,该 app 会 TCP 长连接远程多台主机(不同域名)中的一个。
建立一个特定端口范围的代理服务器(这里用 golang 写的),使用 DNS server(这里使用 coredns) 将指定范围的域名指向代理 IP,其他一切照常,这样手机端只要更改 DNS 即可切换是否走代理,这样影响最小。
因为通讯内容本身没有包含目标地址的信息(类似 HTTP 中的 host
),所以需要 coredns 通过 dnstap 协议的插件将相关查询的域名发过来,代理通过来源 IP 做匹配。
首先是 coredns,只需要很少的几行就可以配置,这里 192.168.1.1
是上游 DNS 地址,app-server.com
是要更改的域名,127.0.0.1
为变化后的解析
(proxy) {
template IN A {
answer "{{ .Name }} 60 IN A 127.0.0.1"
}
log
}
app-server.com {
import proxy
}
. {
forward . 192.168.1.1:53
cache 10800
log
}
这里是泛域名解析(如 foo.bar.app-server.com
也同样会指到 127.0.0.1
)
注意 DNS server 要独占 53 端口(理论上可使用任何端口,但通常的网络设置里不会有端口选项),我的解决方式是用虚拟机另占一个 IP 地址,当然相应的更改后 IP 也要做相应更改。
启动后可以通过 dig
命令确认 DNS 是否生效,如
dig foo.bar.app-server.com @127.0.0.1
启用 dnstap,只需要简单的在配置开头 (proxy) {
段里增加一行
dnstap tcp://127.0.0.1:6000 full
这样所有查询 log 会通过长连接实时汇报给代理服务器。相应的,代理服务器需要监听 6000
端口。这里讲下 golang
里的具体实现。
通过 net.Listen("tcp", 6000)
并轮询 Accept()
会获得 dnstap 连过来的 net.Conn
,因为是 TCP 长连接,所以需要解决基本的分帧,在看了 dnstap 的源代码后得知使用的是 golang-framestream,分帧后,实际通讯用的是 protobuf,dnstap 提供了对应的 .proto
文件 反序列化。这部分具体代码如下
func dnstapConn(c net.Conn) {
defer c.Close()
reader, err := framestream.NewReader(c,
&framestream.ReaderOptions{
ContentTypes: [][]byte{[]byte("protobuf:dnstap.Dnstap")},
Bidirectional: true,
})
if err != nil {
return
}
ab := make([]byte, 8192)
for {
n, err := reader.ReadFrame(ab)
if err != nil {
break
}
d := &dnstap.Dnstap{}
err = proto.Unmarshal(ab[:n], d)
if err != nil {
continue
}
需要解释一下,d := &dnstap.Dnstap{}
是我将其 .proto
文件加了一行 option go_package = ".;dnstap";
并编译为 dnstap/dnstap.pb.go
文件,以上操作获取了完整的信息,之后具体调用就很简单了
msg := d.GetMessage()
if msg.GetType() != dnstap.Message_CLIENT_QUERY {
continue
}
dm := new(dns.Msg)
err = dm.Unpack(msg.QueryMessage)
if err != nil || len(dm.Question) < 1 {
continue
}
domain := dm.Question[0].Name
clientIP := net.IP(msg.QueryAddress).String()
这里的 dns
实际是用的 miekg/dns
库。最终得到了想要的两个变量 domain
和 clientIP
之后就是根据 DNS 查询结果和实际对代理的连接做匹配后就可以连接目标服务器了。
其实最初是考虑解析 coredns 的文本 log,但实际搞明白 dnstap 用的几个库之后,这种方式从效率上更好,代码也更简练(不需要处理很多关于文本操作的异常)。
另外需要注意,如果代理服务器重启后,coredns 并不会马上重连,解决方式是代理启动后就立即执行三次针对要代理的域名的 dig
操作,这样 coredns 会及时发现连接问题并重连。