我的目标是从Windows计算机上发现本地网络中Linux计算机“服务器”的IP地址。从另一台Linux计算机“客户机”我可以做到:
ping -c1 server.local
得到答复。“服务器”和“客户机”都运行avahi,所以这很容易。但是,我想从我的一个python应用程序中发现“server”的IP地址,这个应用程序同时运行在MS Windows和Linux计算机上。注意:在不运行MDNS软件的MS Windows计算机上,没有主机名解析(显然,
ping
在所述Windows系统上不起作用)。我知道存在pyzeroconf,这是我尝试使用的模块;但是,文档很少,对我没有太大帮助。使用类似于
avahi-discover
的工具,我发现计算机发布MDNS类型的服务类型_workstation._tcp.local.
的记录(显然是虚拟端口9,丢弃服务),这可能等同于DNS A记录。或者我可能完全误解了MDN机制。如何通过python中的MDN发现计算机的IP地址(或获取计算机的IP地址列表)?
澄清(基于评论)
显而易见的
PTR
在运行并配置为使用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
最佳答案
如果有人仍然对此感兴趣,可以在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忽略(或不接收)答案。