需求:对 DNS 查询进行转发和缓存的本地 DNS 服务器。

补充 1:提供一个记录管理的接口(HTTP handler)。

补充 2:提供一个名字(name)。

DNS 服务器的相关要点如下:

  • DNS 服务器把域名转换为 IP。
  • DNS 主要使用 UDP 协议,其端口为 53。
  • DNS 消息的长度最多为 512 字节,若超过这个长度,则必须使用 EDNS。

需要的组成部分有:

  • UDP
  • DNS 消息解析器(DNS message parser)
  • 转发
  • 缓存
  • HTTP handler

我们的解决方案是:

netgolang.org/x/net/dns/dnsmessagegob
conn
conn, _ = net.ListenUDP("udp", &net.UDPAddr{Port: 53})
defer conn.Close()
for {
    buf := make([]byte, 512)
    _, addr, _ := conn.ReadFromUDP(buf)
    ...
}

解析报文,检查是否是 DNS 消息。

var m dnsmessage.Message
err = m.Unpack(buf)

如果你想知道一条 DNS 消息长什么样,请查看下图:

转发消息到公共解析器

// re-pack
packed, err = m.Pack()
resolver := net.UDPAddr{IP: net.IP{1, 1, 1, 1}, Port: 53}
_, err = conn.WriteToUDP(packed, &resolver)

公共解析器会返回一条 anwser,我们会抓取信息,返回给客户端。

if m.Header.Response {
    packed, err = m.Pack()
    _, err = conn.WriteToUDP(packed, &addr)
}
connWriteToUDP

存储 answer

RWMutex
func questionToString(q dnsmessage.Question) string {
    ...
}
type store struct {
    sync.RWMutex
    data      map[string][]dnsmessage.Resource
}
q := m.Questions[0]
var s store
s.Lock()
s.data[questionToString(q)] = m.Answers
s.Unlock()

持久化缓存(persistent cache)

s.datagob
f, err := os.Create(filepath.Join("path", "file"))
enc := Gob.NewEncoder(f)
err = enc.Encode(s.data)

需要注意,gob 在编码前需要知道数据类型。

func INIt() {
    Gob.Register(&dnsmessage.AResource{})
    ...
}

记录管理

这个相对来说就比较简单了,Create handler 如下所示:

type request struct {
    Host string
    TTL  uint32
    Type string
    Data string
}
func toResource(req request) (dnsmessage.Resource, error) {
    ...
}
// POST handler
err = JSON.NewDecoder(r.Body).Decode(&req)
// transform req to a dnsmessage.Resource
r, err := toResource(req)
// write r to the store

理所当然是吧?

完整的代码在。我将其命名为 rind(REST interface name domain)。

以上就是用 Go 实现这个网络程序的简述。

欢迎任何反馈。编程愉快,Gopher !


本文由 原创编译, 荣誉推出