mDNS
mDNS 即组播 DNS (multicast DNS)
主要实现了零配置,在局域网内主机间的相互发现
苹果的 Bonjour 就是一个基于 mDNS 的产品
hashicorp/mdns
一个 golang 版本,基于组播 DNS 信息,来实现服务发现的开源库
github 地址: https://github.com/hashicorp/mdns
没有实现 mDNS 的所有协议,且无法与 Bonjour 的产品相互发现
但是作为小规模局域网内服务发现
功能,已足够用
该库主要目的用于对开发时、演示 demo 时,提供便利。请勿用于生产环境
实现思路
hashicorp/mdns 实现思路很简单,就 1 个协议,见下图
+--------------+ 2. udp reply dns info
| |
| Client +<-----------------------------------------+--------------------+--------------------------+
| | ^ ^ ^
+------+-------+ | | |
| | | |
| | | |
| +-------+-------+ +--------+------+ +--------+------+
| | | | | | |
| | Server1 | | Server2 | ... | ServerN |
| | | | | | |
| +-------+-------+ +--------+------+ +--------+------+
| ^ ^ ^
| 1. udp multicast requset dns info | | |
| | | |
+--------------------------------------------------+--------------------+--------------------------+
- 客户端 udp 组播
requset dns info
给整个局域网 - 服务器 udp 监听组播地址 (224.0.0.251:5353),并应答来至客户端
requset dns info
请求
源代码分析
1. 目录文件介绍
.
├── LICENSE
├── README.md
├── client.go // 客户端 API
├── go.mod
├── go.sum
├── server.go // 服务器 API
├── server_test.go
├── zone.go // DNS 协议相关
└── zone_test.go
2. server.go 代码分析
-
监听组播地址 (224.0.0.251:5353)
// NewServer is used to create a new mDNS server from a config func NewServer(config *Config) (*Server, error) { // Create the listeners ipv4List, _ := net.ListenMulticastUDP("udp4", config.Iface, ipv4Addr) // 监听地址 ipv6List, _ := net.ListenMulticastUDP("udp6", config.Iface, ipv6Addr) // Check if we have any listener if ipv4List == nil && ipv6List == nil { return nil, fmt.Errorf("No multicast listeners could be started") } s := &Server{ config: config, ipv4List: ipv4List, ipv6List: ipv6List, shutdownCh: make(chan struct{}), } // 接收消息协程 if ipv4List != nil { go s.recv(s.ipv4List) } if ipv6List != nil { go s.recv(s.ipv6List) } return s, nil }
学渣同学(比如我)可能会在这里有疑问:多个进程都监听同一个地址
组播地址 (224.0.0.251:5353)
,不会冲突吗 = =| -
接收消息处理
代码都是非常简单,摘录最重要的func (s *Server) handleQuestion(q dns.Question) (multicastRecs, unicastRecs []dns.RR) { records := s.config.Zone.Records(q) // 细节代码略 return records, nil }
从 s.recv 协程开始,每个消息处理,主要在 handleQuestion 函数中的
s.config.Zone.Records(q)
内,对 DNS 协议做解析,并组装 DNS 协议应答
3. zone.go
文件命名有点奇怪,实际上就是 DNS 协议解析处理并应答(且真正只有 1 个协议处理)
本文件中的 MDNSService 结构体,就是本服务信息,最终会组装成 DNS 信息发给客户端
剩余的就是DNS 协议做解析,并组装 DNS 协议应答
,代码如下:
// Records returns DNS records in response to a DNS question.
func (m *MDNSService) Records(q dns.Question) []dns.RR {
switch q.Name {
case m.enumAddr:
return m.serviceEnum(q)
case m.serviceAddr: // 只有这个分支,客户端会发。其他分支内容, m.serviceRecords(q) 内都会组装好发给客户端
return m.serviceRecords(q)
case m.instanceAddr:
return m.instanceRecords(q)
case m.HostName:
if q.Qtype == dns.TypeA || q.Qtype == dns.TypeAAAA {
return m.instanceRecords(q)
}
fallthrough
default:
return nil
}
}
func (m *MDNSService) instanceRecords(q dns.Question) []dns.RR {
// 内容太多略
// m.serviceRecord(q) 内部会调用本函数
// 本函数会递归调用多次,把消息组装好
// 实际上本函数所有 switch 内所有分支内容都会被递归调用到。把所有 DNS 信息组装好
}
3. client.go
-
建立到组播地址 (224.0.0.251:5353)的连接(虚)
// NewClient creates a new mdns Client that can be used to query // for records func newClient() (*client, error) { // TODO(reddaly): At least attempt to bind to the port required in the spec. // Create a IPv4 listener uconn4, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) if err != nil { log.Printf("[ERR] mdns: Failed to bind to udp4 port: %v", err) } uconn6, err := net.ListenUDP("udp6", &net.UDPAddr{IP: net.IPv6zero, Port: 0}) if err != nil { log.Printf("[ERR] mdns: Failed to bind to udp6 port: %v", err) } if uconn4 == nil && uconn6 == nil { return nil, fmt.Errorf("failed to bind to any unicast udp port") } mconn4, err := net.ListenMulticastUDP("udp4", nil, ipv4Addr) if err != nil { log.Printf("[ERR] mdns: Failed to bind to udp4 port: %v", err) } mconn6, err := net.ListenMulticastUDP("udp6", nil, ipv6Addr) if err != nil { log.Printf("[ERR] mdns: Failed to bind to udp6 port: %v", err) } if mconn4 == nil && mconn6 == nil { return nil, fmt.Errorf("failed to bind to any multicast udp port") } c := &client{ ipv4MulticastConn: mconn4, ipv6MulticastConn: mconn6, ipv4UnicastConn: uconn4, ipv6UnicastConn: uconn6, closedCh: make(chan struct{}), } return c, nil }
学渣同学(比如我)可能会在这里有疑问:建立组播地址连接,也用 net.ListenMulticastUDP 吗 = =|
-
请求局域网内某服务的 DNS 信息
func (c *client) query(params *QueryParam) error { // 代码略 }
-
接收服务器应答处理
func (c *client) recv(l *net.UDPConn, msgCh chan *dns.Msg) { // 代码略 }
server.go zone.go 看懂后, client.go 就相对简单了
需要注意的一点是:query 内部会开启 recv 协程,这里该库应该做下优化, newClient 函数内开启一根常驻 recv 协程更好
其他
mDNS 可作为服务发现的一个补充,在 demo 演示 、开发环境,会对服务器端带来便利
前提是你的服务发现功能要能插件化,方便生产环境中使用诸如 etcd 、 consule 这种比较稳定的产品