如何在 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 机器上的 nscdnslcd 可以做到这一点。它也可以由配置为缓存的本地名称服务器缓存(从前的常见设置。现在可能没有那么多)。不幸的是,这不是一个直截了当的“不”回答。这些东西很少。 :) 这只会返回一个地址,不是吗?因此,如果您有一个 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.comexample.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 网络服务器中禁用反向 dns 查找?

python 当其他工具(dig,host,nslookup)不可用时,使用python进行快速dns查找

Python:如何使用特定的 DNS 服务器查找带有 IP 的主机名?

SPF 记录中的 DNS 查找过多

如何在 dns-python 中进行 dns 查询作为 dig(带有附加记录部分)?

DNS服务的配置与管理---配置正向查找区域