简体   繁体   English

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

[英]How can I find the IP address of a host using mdns?

My target is to discover the IP address of a Linux computer "server" in the local.network from a Windows computer.我的目标是从 Windows 计算机发现 local.network 中 Linux 计算机“服务器”的 IP 地址。 From another Linux computer "client" I can do:从另一台 Linux 计算机“客户端”我可以这样做:

ping -c1 server.local

and get a reply.并得到回复。 Both "server" and "client" run Avahi, so this is easy. “服务器”和“客户端”都运行 Avahi,所以这很容易。 However, I would like to discover the IP address of "server" from a Python application of mine, which runs on both MS Windows and Linux computers.但是,我想从我的 Python 应用程序中发现“服务器”的 IP 地址,该应用程序在 MS Windows 和 Linux 计算机上运行。 Note: on MS Windows computers that do not run mDNS software, there is no hostname resolution (and obviously ping does not work on said Windows systems).注意:在运行 mDNS 软件的 MS Windows 计算机上,没有主机名解析(显然ping在所述 Windows 系统上不起作用)。

I know of the existence of pyzeroconf , and this is the module I tried to use;我知道pyzeroconf的存在,这是我尝试使用的模块; however, the documentation is scarce and not very helpful to me.但是,文档很少,对我帮助不大。 Using tools like avahi-discover , I figured that computers publish records of the service type _workstation._tcp.local.使用像avahi-discover这样的工具,我发现计算机发布了服务类型_workstation._tcp.local. (with the obviously dummy port 9, the discard service) of mDNS type PTR that might be the equivalent of a DNS A record. (带有明显的虚拟端口 9,丢弃服务)mDNS 类型PTR ,可能相当于 DNS A 记录。 Or I might have misunderstood completely the mDNS mechanism.或者我可能完全误解了 mDNS 机制。

How can I discover the IP address of a computer (or get a list of IP addresses of computers) through mDNS from Python?如何通过mDNS从Python发现一台计算机的IP地址(或者得到计算机的IP地址列表)?

CLARIFICATION (based on a comment)澄清(基于评论)

The obvious socket.gethostbyname works on a computer running and configured to use mDNS software (like Avahi):显而易见的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'

However, on Windows computers not running mDNS software (the default), I get:但是,在未运行 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

In case somebody is still interested in this, the task can be accomplished, on Windows and Linux, using dnspython as follows:如果有人仍然对此感兴趣,可以在 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.'

This code works when the target computer runs avahi, but fails when the target runs python zeroconf or the esp8266 mdns implementation.此代码在目标计算机运行 avahi 时有效,但在目标运行 python zeroconf 或 esp8266 mdns 实现时失败 Interestingly Linux systems running avahi successfully resolve such targets (avahi apparently implementing nssswitch.conf mdns plugin and being a fuller implementation of the mdns protocol)有趣的是,运行 avahi 的 Linux 系统成功地解决了这些目标(avahi 显然实现了 nssswitch.conf mdns 插件并且是 mdns 协议的更完整实现)
In case of a naive mdns responder which, contrary to the rfc, sends its response via the mdns port, the following code (run on linux and windows and resolving linux avahi, hp printer and esp8266 targets) works for me: (and is also non-compliant as it uses the MDNS port to send the query while it is obviously NOT a full implementation)如果一个天真的 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


The empty dns object was created in the above code by passing the constructor a string collected from the network using空的 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'

This query was produced by nslookup so its id was non-zero (in this case \\xf6\\xe8) trying to resolve esp01.local.此查询是由 nslookup 生成的,因此它的 id 非零(在本例中为 \\xf6\\xe8)试图解析 esp01.local。 An dns object containing an empty query was then created by:然后通过以下方式创建了一个包含空查询的 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'

The same result could also be created by:也可以通过以下方式创建相同的结果:

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

The dns object could also be created with non-empty query:也可以使用非空查询创建 dns 对象:

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

or even with multiple queries:甚至有多个查询:

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

but minimal responders may fail to handle dns messages containing multiple queries但最少的响应者可能无法处理包含多个查询的 dns 消息


I am also unhappy with the python zeroconf documentation.我也对 python zeroconf 文档不满意。 From a casual reading of the code and packet monitoring using tcpdump, it seems that (when the registration example is running) zeroconf will respond to address queries but nslookup ignores (or does not receive) the answer.从使用 tcpdump 随意阅读代码和数据包监控来看,似乎(当注册示例运行时)zeroconf 将响应地址查询,但 nslookup 忽略(或不接收)答案。

Sticking to the letter of the original question, the answer is a qualified yes.坚持原始问题的字母,答案是肯定的。 Targets running avahi can be discovered with python zeroconf provided that they advertise some service.可以使用 python zeroconf 发现运行 avahi 的目标,前提是它们宣传某些服务。 By default the avahi-deamon advertises the _workstation._tcp.local service.默认情况下,avahi-deamon 通告_workstation._tcp.local服务。 In order to discover such servers modify the browser.py example coming with zeroconf so that it looks for this service (or any other service advertised by the targets of interest) instead of (or in addition to) _http._tcp.local .为了发现这样的服务器,修改带有 zeroconf 的 browser.py 示例,以便它查找此服务(或感兴趣的目标广告的任何其他服务)而不是(或除此之外) _http._tcp.local browser.py will also discover targets using zeroconf's registration.py example to advertise their services, but not esp8266 targets (esp8266 responds with a malformed message to TXT (16) query). browser.py 还将使用 zeroconf 的 registration.py 示例来发现目标来宣传其服务,但不会发现 esp8266 目标(esp8266 以格式错误的消息响应 TXT (16) 查询)。

#!/usr/bin/env python
from __future__ import absolute_import, division, print_function, unicode_literals
""" Example of resolving local hosts"""
# a stripped down verssion of browser.py example
# zeroconf may have issues with ipv6 addresses and mixed case hostnames
from time import sleep

from zeroconf import ServiceBrowser, ServiceStateChange, Zeroconf,DNSAddress

def on_service_state_change(zeroconf, service_type, name, state_change):
    if state_change is ServiceStateChange.Added:
        zeroconf.get_service_info(service_type, name)

zeroconf = Zeroconf()
ServiceBrowser(zeroconf, "_workstation._tcp.local.", handlers=[on_service_state_change])
ServiceBrowser(zeroconf, "_telnet._tcp.local.", handlers=[on_service_state_change])
ServiceBrowser(zeroconf, "_http._tcp.local.", handlers=[on_service_state_change])
ServiceBrowser(zeroconf, "_printer._tcp.local.", handlers=[on_service_state_change])
sleep(2)
#lookup specific hosts
print(zeroconf.cache.entries_with_name('esp01.local.'))
print(zeroconf.cache.entries_with_name('microknoppix.local.'))
print(zeroconf.cache.entries_with_name('pvknoppix.local.'))
print(zeroconf.cache.entries_with_name('debian.local.'))
cache=zeroconf.cache.cache
zeroconf.close()
# list all known hosts in .local
for key in cache.keys():
    if isinstance(cache[key][0],DNSAddress):
       print(key,cache[key])
sleep(1)
#output follows
#[10.0.0.4]
#[10.0.0.7]
#[]
#[3ffe:501:ffff:100:a00:27ff:fe6f:1bfb, 10.0.0.6]
#debian.local. [3ffe:501:ffff:100:a00:27ff:fe6f:1bfb, 10.0.0.6]
#esp01.local. [10.0.0.4]
#microknoppix.local. [10.0.0.7]

But to be honest I would not use zeroconf for this.但老实说,我不会为此使用 zeroconf。

If you just wanted a one liner for bash in Linux:如果你只想要 Linux 中 bash 的一个班轮:

getent hosts HOSTNAME.local | awk '{ print $1 }'

Make sure to replace HOSTNAME with the hostname you are looking for.确保将HOSTNAME替换为您要查找的主机名。

If you can ping the system why not use subprocess, redirect the output to a file, read the file and be voila!如果您可以 ping 系统为什么不使用子进程,将输出重定向到一个文件,读取该文件,瞧! Here is a rough sketch:这是一个粗略的草图:

import subprocess
server = 'localhost'
cmd = 'ping -c 5 %s &> hostname_ping.txt' % server
proc = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
ret = proc.wait()
if ret != 0:
    # Things went horribly wrong!
    #NOTE: You could also do some type of retry.
    sys.exit(ret)
f = open('hostname_ping.txt')
ip = f.next().split(' ')[3][:-1]

NOTE: In my case ip will be 127.0.0.1, but that is because I used localhost.注意:在我的情况下,ip 将是 127.0.0.1,但那是因为我使用了 localhost。 Also you could make the ping count 1, but I made it 5 just incase there were any network issues.您也可以将 ping 计数设为 1,但我将其设为 5,以防万一有任何网络问题。 You will have to be smarter about how you parse the file.您必须更聪明地解析文件。 For that you could use the re module.为此,您可以使用 re 模块。

Anyway, the method described here is crude, but it should work if you know the name of the system that you want to ping.无论如何,这里描述的方法很粗糙,但是如果您知道要 ping 的系统的名称,它应该可以工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM