hashicorp/mdns 介绍与源代码分析

本文介绍了hashicorp/mdns,一个基于Golang的mDNS服务发现库,用于局域网内的零配置主机发现。虽然不完全实现mDNS协议,但适用于小型开发和演示环境。文章深入源代码,分析了server.go、zone.go和client.go,讲解了服务监听、DNS解析和客户端请求处理等关键部分。
摘要由CSDN通过智能技术生成

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 这种比较稳定的产品

以下是一个使用C语言实现lwIP mDNS的示例代码: ```c #include "lwip/init.h" #include "lwip/netif.h" #include "lwip/dns.h" #include "lwip/mdns.h" void mdns_init() { struct netif* netif; ip_addr_t addr; // 初始化lwIP库 lwip_init(); // 添加网络接口(根据需要修改接口名称) netif = netif_add(NULL, NULL, NULL, NULL, NULL, NULL); // 设置网络接口的IP地址 IP4_ADDR(&addr, 192, 168, 1, 10); netif_set_ipaddr(netif, &addr); netif_set_up(netif); // 启动mDNS服务 mdns_resp_init(); mdns_resp_add_netif(netif, "mydevice"); // 设置mDNS服务的网络接口和设备名称 mdns_resp_announce(netif); // 发送mDNS通告 // 在此处添加其他的网络服务初始化代码,如HTTP服务器、FTP服务器等 } int main() { // 初始化mDNS mdns_init(); // 进入主循环 while(1) { // 在此处添加主循环处理代码 // 处理网络任务 sys_check_timeouts(); } return 0; } ``` 这段示例代码通过lwIP库实现了mDNS服务。首先,需要调用lwip_init()函数初始化lwIP库。然后,添加网络接口并设置IP地址。接下来,调用mdns_resp_init()函数初始化mDNS响应器,并通过mdns_resp_add_netif()函数将网络接口和设备名称添加到mDNS响应器中。最后,调用mdns_resp_announce()函数发送mDNS通告。 在主循环中,可以添加其他的网络任务处理代码。通过调用sys_check_timeouts()函数处理lwIP库的定时任务。 请根据你的实际需求修改示例代码中的网络接口和设备名称等参数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fananchong2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值