如何使用 mdns 查找主机的 IP 地址?

新手上路,请多包涵

我的目标是从 Windows 计算机发现本地网络中 Linux 计算机“服务器”的 IP 地址。从另一台 Linux 计算机“客户端”我可以这样做:

 ping -c1 server.local

并得到回复。 “服务器”和“客户端”都运行 Avahi,所以这很容易。但是,我想从我的 Python 应用程序中发现“服务器”的 IP 地址,该应用程序在 MS Windows 和 Linux 计算机上运行。注意:在 运行 mDNS 软件的 MS Windows 计算机上,没有主机名解析(显然 ping 在所述 Windows 系统上不起作用)。

我知道 pyzeroconf 的存在,这是我尝试使用的模块;但是,文档很少,对我帮助不大。使用像 avahi-discover 这样的工具,我发现计算机发布了 mDNS 类型的服务类型 _workstation._tcp.local. (带有明显的虚拟端口 9,丢弃服务)的记录 PTR 这可能相当于 DNS A 记录。或者我可能完全误解了 mDNS 机制。

如何通过 Python 的 mDNS 发现计算机的 IP 地址(或获取计算机的 IP 地址列表)?

澄清(基于评论)

显而易见的 socket.gethostbyname 在运行并配置为使用 mDNS 软件(如 Avahi)的计算机上工作:

 Python 2.6.5 (r265:79063, Apr 16 2010, 13:09:56)
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> socket.gethostbyname('server.local')
'192.168.42.42'

但是,在未运行 mDNS 软件(默认)的 Windows 计算机上,我得到:

 Python 2.7.1 (r271:86832, Nov 27 2010, 18:30:46) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> socket.gethostbyname('server.local')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
socket.gaierror: [Errno 11001] getaddrinfo failed

原文由 tzot 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.7k
1 个回答

如果有人对此仍然感兴趣,可以在 Windows 和 Linux 上使用 dnspython 完成任务,如下所示:

 import dns.resolver
myRes=dns.resolver.Resolver()
myRes.nameservers=['224.0.0.251'] #mdns multicast address
myRes.port=5353 #mdns port
a=myRes.query('microknoppix.local','A')
print a[0].to_text()
#'10.0.0.7'
a=myRes.query('7.0.0.10.in-addr.arpa','PTR')
print a[0].to_text()
#'Microknoppix.local.'

此代码在目标计算机运行 avahi 时有效,但在目标运行 python zeroconf 或 esp8266 mdns 实现时 失败。有趣的是,运行 avahi 的 Linux 系统成功地解决了这些目标(avahi 显然实现了 nssswitch.conf mdns 插件并且是 mdns 协议的更完整实现)

如果是天真的 mdns 响应器,它与 rfc 相反,通过 mdns 端口发送响应,以下代码(在 linux 和 windows 上运行并解析 linux avahi、hp 打印机和 esp8266 目标)对我有用:(并且也是 不兼容,因为它使用 MDNS 端口发送查询,而它显然 不是 完整的实现)

 import socket
import struct
import dpkt, dpkt.dns
UDP_IP="0.0.0.0"
UDP_PORT=5353
MCAST_GRP = '224.0.0.251'
sock = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind( (UDP_IP,UDP_PORT) )
#join the multicast group
mreq = struct.pack("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
for host in ['esp01','microknoppix','pvknoppix','hprinter'][::-1]:
#    the string in the following statement is an empty query packet
     dns = dpkt.dns.DNS('\x00\x00\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01')
     dns.qd[0].name=host+'.local'
     sock.sendto(dns.pack(),(MCAST_GRP,UDP_PORT))
sock.settimeout(5)
while True:
  try:
     m=sock.recvfrom( 1024 );#print '%r'%m[0],m[1]
     dns = dpkt.dns.DNS(m[0])
     if len(dns.qd)>0:print dns.__repr__(),dns.qd[0].name
     if len(dns.an)>0 and dns.an[0].type == dpkt.dns.DNS_A:print dns.__repr__(),dns.an[0].name,socket.inet_ntoa(dns.an[0].rdata)
  except socket.timeout:
     break
#DNS(qd=[Q(name='hprinter.local')]) hprinter.local
#DNS(qd=[Q(name='pvknoppix.local')]) pvknoppix.local
#DNS(qd=[Q(name='microknoppix.local')]) microknoppix.local
#DNS(qd=[Q(name='esp01.local')]) esp01.local
#DNS(an=[RR(name='esp01.local', rdata='\n\x00\x00\x04', ttl=120, cls=32769)], op=33792) esp01.local 10.0.0.4
#DNS(an=[RR(name='PVknoppix.local', rdata='\n\x00\x00\xc2', ttl=120, cls=32769)], op=33792) PVknoppix.local 10.0.0.194

通过向构造函数传递从网络收集的字符串,在上面的代码中创建了空的 dns 对象

m0=sock.recvfrom( 1024 );print '%r'%m0[0]
#'\xf6\xe8\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x05esp01\x05local\x00\x00\x01\x00\x01'

此查询由 nslookup 生成,因此其 id 为非零(在本例中为 \xf6\xe8)试图解析 esp01.local。然后通过以下方式创建包含空查询的 dns 对象:

 dns = dpkt.dns.DNS(m0[0])
dns.id=0
dns.qd[0].name=''
print '%r'%dns.pack()
#'\x00\x00\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01'

同样的结果也可以通过以下方式创建:

 dns=dpkt.dns.DNS(qd=[dpkt.dns.DNS.Q(name='')])

也可以使用非空查询创建 dns 对象:

 dns=dpkt.dns.DNS(qd=[dpkt.dns.DNS.Q(name='esp01.local')])

甚至有多个查询:

 dns=dpkt.dns.DNS(qd=[dpkt.dns.DNS.Q(name='esp01.local'),dpkt.dns.DNS.Q(name='esp02.local')])

但最少的响应者可能无法处理包含多个查询的 dns 消息

我也不满意 python zeroconf 文档。从随意阅读代码和使用 tcpdump 的数据包监控来看,似乎(当注册示例运行时)zeroconf 将响应地址查询,但 nslookup 忽略(或不接收)答案。

原文由 NameOfTheRose 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题