如何在 Python 中进行 DNS 查找,包括引用 /etc/hosts?
Posted
技术标签:
【中文标题】如何在 Python 中进行 DNS 查找,包括引用 /etc/hosts?【英文标题】:How can I do DNS lookups in Python, including referring to /etc/hosts? 【发布时间】:2011-02-17 19:31:26 【问题描述】:dnspython 可以很好地完成我的 DNS 查找,但它完全忽略了 /etc/hosts
的内容。
是否有一个 python 库调用可以做正确的事情?即首先检查etc/hosts
,否则只回退到DNS查找?
【问题讨论】:
我为此创建了一个问题:github.com/rthalley/dnspython/issues/149 dnspython 不会实现这个。对于简单的正向查找,使用建议的socket.gethostbyname
,对于更复杂的查询,使用 dnspython。
【参考方案1】:
我不确定您是要自己进行 DNS 查找,还是只需要主机的 ip。如果你想要后者,
/!\ socket.gethostbyname 已弃用,首选socket.getaddrinfo
来自man gethostbyname
:
gethostbyname*()、gethostbyaddr*()、[...] 函数已过时。应用程序应使用 getaddrinfo(3)、getnameinfo(3),
import socket
print(socket.gethostbyname('localhost')) # result from hosts file
print(socket.gethostbyname('google.com')) # your os sends out a dns query
【讨论】:
有谁知道这个查找缓存在哪个级别?在 Python 中?还是操作系统?还是 DNS 服务器? @Simon Python 和操作系统都没有缓存。这取决于所涉及的任何 DNS 服务器是否缓存。 – 一般而言:DNS 仅由应用程序本身缓存,或由解析链中的解析 DNS 服务器缓存。 @Jochen “localhost”是否来自主机文件取决于配置! @RobertSiemer 抱歉迟到的评论:结果可能被本地解析器缓存。 Unix 机器上的nscd
和 nslcd
可以做到这一点。它也可以由配置为缓存的本地名称服务器缓存(从前的常见设置。现在可能没有那么多)。不幸的是,这不是一个直截了当的“不”回答。这些东西很少。 :)
这只会返回一个地址,不是吗?因此,如果您有一个 dns 循环,这将不会公开与主机名关联的所有地址。【参考方案2】:
Python 中的正常名称解析工作正常。为什么你需要 DNSpython。只需使用socket 的getaddrinfo
,它遵循为您的操作系统配置的规则(在Debian 上,它遵循/etc/nsswitch.conf
:
>>> print(socket.getaddrinfo('google.com', 80))
[(10, 1, 6, '', ('2a00:1450:8006::63', 80, 0, 0)), (10, 2, 17, '', ('2a00:1450:8006::63', 80, 0, 0)), (10, 3, 0, '', ('2a00:1450:8006::63', 80, 0, 0)), (10, 1, 6, '', ('2a00:1450:8006::68', 80, 0, 0)), (10, 2, 17, '', ('2a00:1450:8006::68', 80, 0, 0)), (10, 3, 0, '', ('2a00:1450:8006::68', 80, 0, 0)), (10, 1, 6, '', ('2a00:1450:8006::93', 80, 0, 0)), (10, 2, 17, '', ('2a00:1450:8006::93', 80, 0, 0)), (10, 3, 0, '', ('2a00:1450:8006::93', 80, 0, 0)), (2, 1, 6, '', ('209.85.229.104', 80)), (2, 2, 17, '', ('209.85.229.104', 80)), (2, 3, 0, '', ('209.85.229.104', 80)), (2, 1, 6, '', ('209.85.229.99', 80)), (2, 2, 17, '', ('209.85.229.99', 80)), (2, 3, 0, '', ('209.85.229.99', 80)), (2, 1, 6, '', ('209.85.229.147', 80)), (2, 2, 17, '', ('209.85.229.147', 80)), (2, 3, 0, '', ('209.85.229.147', 80))]
【讨论】:
添加转换步骤会很好。addrs = [ str(i[4][0]) for i in socket.getaddrinfo(name, 80) ]
给了我 ips 列表。
我的请求结果多次返回同一个ip地址;我们可以通过对 alex 的建议进行简单更改来解决这个问题:addrs = str(i[4][0]:i for i in socket.getaddrinfo(name, 80)
返回一个包含唯一 ips 作为键的 dict,其余结果配对。【参考方案3】:
听起来您不想自己解析 dns(这可能是错误的命名法)dnspython 似乎是一个独立的 dns 客户端,可以理解的是它会忽略您的操作系统,因为它绕过了操作系统的实用程序。
我们可以查看一个名为 getent
的 shell 实用程序来了解(debian 11 类似)操作系统如何为程序解析 dns,这可能是所有使用套接字实现的类 *nix 系统的标准。
查看man getent
的“主机”部分,其中提到了getaddrinfo
的使用,我们可以将其视为man getaddrinfo
要在 python 中使用它,我们必须从数据结构中提取一些信息
.
import socket
def get_ipv4_by_hostname(hostname):
# see `man getent` `/ hosts `
# see `man getaddrinfo`
return list(
i # raw socket structure
[4] # internet protocol info
[0] # address
for i in
socket.getaddrinfo(
hostname,
0 # port, required
)
if i[0] is socket.AddressFamily.AF_INET # ipv4
# ignore duplicate addresses with other socket types
and i[1] is socket.SocketKind.SOCK_RAW
)
print(get_ipv4_by_hostname('localhost'))
print(get_ipv4_by_hostname('google.com'))
【讨论】:
【参考方案4】:list( map( lambda x: x[4][0], socket.getaddrinfo( \
'www.example.com.',22,type=socket.SOCK_STREAM)))
为您提供 www.example.com 的地址列表。 (ipv4 和 ipv6)
【讨论】:
【参考方案5】:此代码适用于返回可能属于特定 URI 的所有 IP 地址。由于许多系统现在处于托管环境(AWS/Akamai/等)中,系统可能会返回多个 IP 地址。 lambda 是从@Peter Silva “借来的”。
def get_ips_by_dns_lookup(target, port=None):
'''
this function takes the passed target and optional port and does a dns
lookup. it returns the ips that it finds to the caller.
:param target: the URI that you'd like to get the ip address(es) for
:type target: string
:param port: which port do you want to do the lookup against?
:type port: integer
:returns ips: all of the discovered ips for the target
:rtype ips: list of strings
'''
import socket
if not port:
port = 443
return list(map(lambda x: x[4][0], socket.getaddrinfo('.'.format(target),port,type=socket.SOCK_STREAM)))
ips = get_ips_by_dns_lookup(target='google.com')
【讨论】:
'.'.format(target)
有什么用? example.com
和 example.com.
似乎都可以正常工作。
@MatthewD.Scholefield '.'.format(target)
构造可以很容易地替换为target
,但我想保留@Peter Silver 提出的原始解决方案。【参考方案6】:
我发现这种方法可以将扩展为 IP 列表的 DNS RR 主机名扩展为成员主机名列表:
#!/usr/bin/python
def expand_dnsname(dnsname):
from socket import getaddrinfo
from dns import reversename, resolver
namelist = [ ]
# expand hostname into dict of ip addresses
iplist = dict()
for answer in getaddrinfo(dnsname, 80):
ipa = str(answer[4][0])
iplist[ipa] = 0
# run through the list of IP addresses to get hostnames
for ipaddr in sorted(iplist):
rev_name = reversename.from_address(ipaddr)
# run through all the hostnames returned, ignoring the dnsname
for answer in resolver.query(rev_name, "PTR"):
name = str(answer)
if name != dnsname:
# add it to the list of answers
namelist.append(name)
break
# if no other choice, return the dnsname
if len(namelist) == 0:
namelist.append(dnsname)
# return the sorted namelist
namelist = sorted(namelist)
return namelist
namelist = expand_dnsname('google.com.')
for name in namelist:
print name
当我运行它时,会列出几个 1e100.net 主机名:
【讨论】:
以上是关于如何在 Python 中进行 DNS 查找,包括引用 /etc/hosts?的主要内容,如果未能解决你的问题,请参考以下文章
python 当其他工具(dig,host,nslookup)不可用时,使用python进行快速dns查找
Python:如何使用特定的 DNS 服务器查找带有 IP 的主机名?