簡體   English   中英

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

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

我的目標是從 Windows 計算機發現 local.network 中 Linux 計算機“服務器”的 IP 地址。 從另一台 Linux 計算機“客戶端”我可以這樣做:

ping -c1 server.local

並得到回復。 “服務器”和“客戶端”都運行 Avahi,所以這很容易。 但是,我想從我的 Python 應用程序中發現“服務器”的 IP 地址,該應用程序在 MS Windows 和 Linux 計算機上運行。 注意:在運行 mDNS 軟件的 MS Windows 計算機上,沒有主機名解析(顯然ping在所述 Windows 系統上不起作用)。

我知道pyzeroconf的存在,這是我嘗試使用的模塊; 但是,文檔很少,對我幫助不大。 使用像avahi-discover這樣的工具,我發現計算機發布了服務類型_workstation._tcp.local. (帶有明顯的虛擬端口 9,丟棄服務)mDNS 類型PTR ,可能相當於 DNS A 記錄。 或者我可能完全誤解了 mDNS 機制。

如何通過mDNS從Python發現一台計算機的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

如果有人仍然對此感興趣,可以在 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 忽略(或不接收)答案。

堅持原始問題的字母,答案是肯定的。 可以使用 python zeroconf 發現運行 avahi 的目標,前提是它們宣傳某些服務。 默認情況下,avahi-deamon 通告_workstation._tcp.local服務。 為了發現這樣的服務器,修改帶有 zeroconf 的 browser.py 示例,以便它查找此服務(或感興趣的目標廣告的任何其他服務)而不是(或除此之外) _http._tcp.local 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]

但老實說,我不會為此使用 zeroconf。

如果你只想要 Linux 中 bash 的一個班輪:

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

確保將HOSTNAME替換為您要查找的主機名。

如果您可以 ping 系統為什么不使用子進程,將輸出重定向到一個文件,讀取該文件,瞧! 這是一個粗略的草圖:

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]

注意:在我的情況下,ip 將是 127.0.0.1,但那是因為我使用了 localhost。 您也可以將 ping 計數設為 1,但我將其設為 5,以防萬一有任何網絡問題。 您必須更聰明地解析文件。 為此,您可以使用 re 模塊。

無論如何,這里描述的方法很粗糙,但是如果您知道要 ping 的系統的名稱,它應該可以工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM