14

私の目標は、Windows コンピューターからローカル ネットワーク内の Linux コンピューター「サーバー」の IP アドレスを検出することです。別の Linux コンピューター「クライアント」から、次のことができます。

ping -c1 server.local

と返事をもらう。「サーバー」と「クライアント」の両方が Avahi を実行するので、これは簡単です。ただし、MS Windows コンピューターと Linux コンピューターの両方で実行される Python アプリケーションから「サーバー」の IP アドレスを見つけたいと考えています。注: mDNS ソフトウェアを実行していないMS Windows コンピューターでは、ホスト名の解決はありpingません (また、明らかに上記の Windows システムでは機能しません)。

私はpyzeroconfの存在を知っています。これは私が使用しようとしたモジュールです。ただし、ドキュメントは少なく、あまり役に立ちません。のようなツールを使用しavahi-discoverて、コンピューターは、DNS A レコードと同等である可能性がある_workstation._tcp.local.mDNS タイプのサービス タイプ (明らかにダミーのポート 9、破棄サービス) のレコードを公開していると考えました。PTRまたは、mDNS メカニズムを完全に誤解している可能性があります。

Python から mDNS を介してコンピュータの IP アドレスを検出する (またはコンピュータの IP アドレスのリストを取得する) にはどうすればよいですか?

明確化(コメントに基づく)

socket.gethostbynamemDNS ソフトウェア (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
4

4 に答える 4

7

誰かがまだこれに興味を持っている場合は、次のようにdnspythonを使用して、Windows と Linux でタスクを実行できます。

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 プロトコルのより完全な実装です)
。次のコード (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 によって生成されたため、esp01.local を解決しようとして、その ID はゼロ以外 (この場合は \xf6\xe8) でした。次に、空のクエリを含む 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 は応答を無視 (または受信しない) しているようです。

于 2016-03-07T20:25:51.767 に答える
4

元の質問の文字に固執すると、答えは限定されたイエスです。avahi を実行しているターゲットは、何らかのサービスをアドバタイズしていれば、python zeroconf で検出できます。デフォルトでは、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 を使用しません。

于 2016-03-28T06:47:06.997 に答える
-4

サブプロセスを使用しない理由をシステムに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 を送信するシステムの名前がわかっている場合は機能するはずです。

于 2012-05-08T22:35:35.970 に答える