如何使用 dnspython 找到域的权威 DNS 服务器?

Posted

技术标签:

【中文标题】如何使用 dnspython 找到域的权威 DNS 服务器?【英文标题】:How can I find the authoritative DNS server for a domain using dnspython? 【发布时间】:2011-05-03 06:28:59 【问题描述】:

作为我正在编写的工具的一部分,我希望有一个诊断程序,它会告诉用户他们是否已为特定服务正确配置了其域的 DNS。我想查询他们的域的权威 DNS 服务器,这样我就可以绕过任何缓存的结果。

【问题讨论】:

【参考方案1】:

我最终来到这里是因为我需要在 python 中获得准确的 ns 返回,忽略缓存并从随机服务器开始。

我从您的回答中得到了很多帮助,以构建对我来说最有用的版本。

它可以让我直接知道网站是否属于停放域(例如 freespt.com => ['ns1.bodis.com.', 'ns2.bodis.com.'])

希望我的代码对你有用!

import random
import sys

import dns.resolver
import dns.name
import dns.message
import dns.query
import dns.flags


import re

NAMESERVERS = 
    '1.1.1.1': 'Cloudflare DNS',
    '1.0.0.1': 'Cloudflare DNS',
    '8.8.8.8': 'Google DNS',
    '8.8.4.4': 'Google DNS',
    '9.9.9.9': 'Quad9',
    '149.112.112.112': 'Quad9',
    '208.67.222.222': 'OpenDNS',
    '208.67.220.220': 'OpenDNS',
    '185.228.168.9': 'CleanBrowsing',
    '185.228.169.9': 'CleanBrowsing',
    '94.140.14.14': 'AdGuard DNS',
    '94.140.15.15': 'AdGuard DNS',


def rand_nameserver():
    """
    Choosing randomly one of the nameservers
    """
    return random.choice(list(NAMESERVERS.keys()))

def query_authoritative_ns(domain):
    """
    Dig recursively leaf to root and retrieve ns with cache bypassing.
    That will NOT only give you the nameserver for the top-level domain !
    It'll try sub-domains if they have NS records.

    It'll tell you what the authoritative DNS server is for www.example.org and will not raise a dns.resolver.NoAnswer exception.

    (quite similary to dig +trace)
    """

    trace = '### Querying authoritative ns for d:\n'.format(d=domain)
    result = None

    resolver = dns.resolver.Resolver(configure=False)
    name_server = rand_nameserver()

    # Setting-up random nameservers
    resolver.nameservers = [name_server]

    # Defining Timeout and lifetime
    # To not taking long time to skip to the next one records when it failed.
    resolver.timeout = 5
    resolver.lifetime = 5

    trace += '[NS records] [d] Used nameserver for DNS NS query is fns (ns)'.format(
        d=domain,
        fns=NAMESERVERS.get(name_server),
        ns=name_server
    )

    ns = resolver.nameservers[0]

    # branches
    n = domain.split('.')

    for i in range(len(n), 0, -1):
        sub = '.'.join(n[i - 1:])

        trace += '\n[NS records] Looking up %s on %s' % (sub, ns)
        query = dns.message.make_query(sub, dns.rdatatype.NS)
        try:
            response = dns.query.udp(query, ns, port=53, timeout=5)

        except Exception as e:

            trace += '\n[NS records] [domain=d] [subdomain=s] Receive exception : e'.format(d=domain, s=sub, e=e)
            continue

        rcode = response.rcode()
        if rcode != dns.rcode.NOERROR:
            if rcode == dns.rcode.NXDOMAIN:

                trace += '\n[NS records] sub does not exist.'.format(sub=sub)
            else:

                trace += '\n[NS records] Error err'.format(err=dns.rcode.to_text(rcode))

            break

        if len(response.authority) > 0:
            rrsets = response.authority
        elif len(response.additional) > 0:
            rrsets = [response.additional]
        else:
            rrsets = response.answer

            # Handle all RRsets, not just the first one
        for rrset in rrsets:
            for rr in rrset:
                if rr.rdtype == dns.rdatatype.SOA:
                    try:

                        trace += '\n[NS records] Same server is authoritative for sub'.format(sub=sub)
                    except KeyError:
                        # Here, for '1337x.unblocked.team' it returns:
                        # "unblocked.team. 300 IN SOA ns1.koaladns.com. admin.unblocked.team. 2021070507 86400 10800 604800 300"
                        return (rrset.to_text().split(' ')[4::11], trace)

                elif rr.rdtype == dns.rdatatype.A:
                    try:
                        ns = rr.items[0].address

                        trace += '\n[NS records] Glue record for sub: gl'.format(sub=rr.name, gl=ns)
                    except KeyError:
                        # <DNS IN A rdata: 103.224.212.63>: None
                        # Glue Record with no data

                        trace += '\n[NS records] Glue record for sub contains None data !'.format(sub=rr.name)
                        # [<DNS ns18.above.com. IN A RRset: [<103.224.212.63>]>, <DNS ns17.above.com. IN A RRset: [<103.224.182.63>]>]

                        # ['ns17.above.com.','3600','IN','A','103.224.182.63','ns18.above.com.','3600','IN','A','103.224.212.63']
                        # And you extract correct ns by jumping arround the list
                        return (' '.join([e.to_text() for e in rrset]).split(' ')[0::5], trace)

                elif rr.rdtype == dns.rdatatype.NS:
                    authority = rr.target

                    try:
                        if sys.version_info.major == 3:
                            ns = resolver.resolve(authority).rrset[0].to_text()
                        else:
                            ns = resolver.query(authority).rrset[0].to_text()

                        trace += '\n[NS records] ns (ns_ip) is authoritative for sub; ttl ttl'.format(ns=authority, ns_ip=ns, sub=sub, ttl=rrset.ttl)
                    except dns.resolver.NoAnswer:

                        trace += '\n[NS records] Got no answer querying ns'.format(ns=authority)
                        continue

                    except dns.resolver.NoNameservers:

                        trace += '\n[NS records]  All nameservers failed to answer the query ns. Retrying with another nameserver.'.format(ns=authority)
                        return query_authoritative_ns(domain)

                    except dns.resolver.NXDOMAIN:
                        # 1337x.full-hd-torrent.net

                        trace += '\n[NS records] ns does not exist'.format(ns=authority)
                        continue

                    except (dns.resolver.Timeout, dns.exception.Timeout):

                        trace += '\n[NS records] [d] Timeout while querying ns, retrying with another nameserver.'.format(d=domain, ns=authority)
                        return query_authoritative_ns(domain)

                    result = rrset.to_text()

                else:
                    # IPv6 glue records etc
                    pass

    if result is not None:
        # Here, rrset can look like:
        # <DNS fp5u7c.top. IN NS RRset: [<justin.ns.cloudflare.com.>, <dora.ns.cloudflare.com.>]>
        # <DNS bypassed.works.prx2.unblocksites.co. IN NS RRset: [<ns2.parklogic.com.>, <ns1.parklogic.com.>]>
        return (re.split(r'NS |\n', result)[1::2], trace)

    return ([], trace)

domain = 'freespt.com'
ns_querying = query_authoritative_ns(domain)
trace = '### Results for ns query_authoritative_ns("d"): ns\ntrace'.format(
    d=domain,
    ns=ns_querying[0],
    trace=ns_querying[1]
    )

print(trace)

这会产生

### Results for ns query_authoritative_ns("freespt.com"): ['ns1.bodis.com.', 'ns2.bodis.com.']
### Querying authoritative ns for freespt.com:
[NS records] [freespt.com] Used nameserver for DNS NS query is Quad9 (149.112.112.112)
[NS records] Looking up com on 149.112.112.112
[NS records] m.gtld-servers.net. (192.55.83.30) is authoritative for com; ttl 41016
[NS records] a.gtld-servers.net. (192.5.6.30) is authoritative for com; ttl 41016
[NS records] b.gtld-servers.net. (192.33.14.30) is authoritative for com; ttl 41016
[NS records] j.gtld-servers.net. (192.48.79.30) is authoritative for com; ttl 41016
[NS records] i.gtld-servers.net. (192.43.172.30) is authoritative for com; ttl 41016
[NS records] c.gtld-servers.net. (192.26.92.30) is authoritative for com; ttl 41016
[NS records] f.gtld-servers.net. (192.35.51.30) is authoritative for com; ttl 41016
[NS records] l.gtld-servers.net. (192.41.162.30) is authoritative for com; ttl 41016
[NS records] g.gtld-servers.net. (192.42.93.30) is authoritative for com; ttl 41016
[NS records] h.gtld-servers.net. (192.54.112.30) is authoritative for com; ttl 41016
[NS records] d.gtld-servers.net. (192.31.80.30) is authoritative for com; ttl 41016
[NS records] e.gtld-servers.net. (192.12.94.30) is authoritative for com; ttl 41016
[NS records] k.gtld-servers.net. (192.52.178.30) is authoritative for com; ttl 41016
[NS records] Looking up freespt.com on 192.52.178.30
[NS records] ns1.bodis.com. (199.59.242.141) is authoritative for freespt.com; ttl 172800
[NS records] ns2.bodis.com. (199.59.242.142) is authoritative for freespt.com; ttl 172800

【讨论】:

【参考方案2】:

我很确定这会做到。

import dns.resolver

domain = 'co.uk'

response = dns.resolver.query(domain, 'SOA')
if response.rrset is not None:
    print response.rrset

你当然可以清理响应

import dns.resolver
import re

domain = 'co.uk'

response = dns.resolver.query(domain, 'SOA')

if response.rrset is not None:
    pattern= r'(%s)\.\s(\d1,)\s(\w+)\sSOA\s(.*?)\.\s(.*?)\.\s(\d1,)\s(\d1,)\s(\d1,)\s(\d1,)\s(\d1,)' % domain
    match = re.match(pattern, str(response.rrset))
    m_name, ttl, class_, ns, email, serial, refresh, retry, expiry, minim = match.groups()

output ='''
Main Name In Zone: a,
Cache TTL: b,
Class: c,
Authoritive NS: d,
Email Address: e,
Last Change: f,
Retry In Secs: g,
Expiry: h,
Slave Cache In Sec: i
'''.format(a = m_name, b = ttl, c = class_, d = ns, e = str(email).replace('\\', ''), f = serial, g = retry, h = expiry, i = minim)

print output

这会产生

Main Name In Zone: co.uk,
Cache TTL: 600,
Class: IN,
Authoritive NS: dns1.nic.uk,
Email Address: hostmaster.nominet.org.uk,
Last Change: 1305857394,
Retry In Secs: 300,
Expiry: 2419200,
Slave Cache In Sec: 10800

【讨论】:

如果我错了,请纠正我,但我相信这些 SOA 结果仍然可以缓存在解析器上,但是?它绕过了使事情变得更加复杂的缓存。【参考方案3】:

如果您只需要名称服务器,其他示例都很好,但过于复杂。示例来自 http://c0deman.wordpress.com/2014/06/17/find-nameservers-of-domain-name-python/:

import dns.resolver

domain = 'google.com'
answers = dns.resolver.query(domain,'NS')
for server in answers:
    print server

【讨论】:

我认为可能会返回缓存结果是否正确?在我的情况下,我特别想找到当前服务器以避免任何缓存,但可能有一种比我这样做更简单的方法。 :) 这只会为您提供***域(或子域,如果它们有 NS 记录)的名称服务器。它不会告诉您www.example.org 的权威 DNS 服务器是什么,并且会引发 dns.resolver.NoAnswer 异常。【参考方案4】:

我遇到了 Jon Colverson 的回答,它帮助我理解了 dnspython 模块以及如何处理结果(我猜所有 DNS 模块都有相同的类结构曲折迷宫......)我需要 TTL 和胶水记录,所以我创建了自己的改编。我把它贴在这里,以防有人发现它有用;我不打算与 Jon Colverson 的出色答案竞争,只是填写一些额外的空白。基本改进是使用答案附加部分中的名称服务器信息(如果可用)。我想服务器可以在附加部分中放置除胶水记录之外的其他内容,因此也许仍应对此进行增强,以正确地将附加部分中的信息与答案部分中的信息相关联。我还获取并打印所有名称服务器,而不仅仅是第一个。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import dns.query
import dns.resolver
from dns.exception import DNSException

def query_authoritative_ns (domain, log=lambda msg: None):

    default = dns.resolver.get_default_resolver()
    ns = default.nameservers[0]

    n = domain.split('.')

    for i in xrange(len(n), 0, -1):
        sub = '.'.join(n[i-1:])

        log('Looking up %s on %s' % (sub, ns))
        query = dns.message.make_query(sub, dns.rdatatype.NS)
        response = dns.query.udp(query, ns)

        rcode = response.rcode()
        if rcode != dns.rcode.NOERROR:
            if rcode == dns.rcode.NXDOMAIN:
                raise Exception('%s does not exist.' % (sub))
            else:
                raise Exception('Error %s' % (dns.rcode.to_text(rcode)))

        if len(response.authority) > 0:
            rrsets = response.authority
        elif len(response.additional) > 0:
            rrsets = [response.additional]
        else:
            rrsets = response.answer

        # Handle all RRsets, not just the first one
        for rrset in rrsets:
            for rr in rrset:
                if rr.rdtype == dns.rdatatype.SOA:
                    log('Same server is authoritative for %s' % (sub))
                elif rr.rdtype == dns.rdatatype.A:
                    ns = rr.items[0].address
                    log('Glue record for %s: %s' % (rr.name, ns))
                elif rr.rdtype == dns.rdatatype.NS:
                    authority = rr.target
                    ns = default.query(authority).rrset[0].to_text()
                    log('%s [%s] is authoritative for %s; ttl %i' % 
                        (authority, ns, sub, rrset.ttl))
                    result = rrset
                else:
                    # IPv6 glue records etc
                    #log('Ignoring %s' % (rr))
                    pass

    return result

import sys

def log (msg):
    sys.stderr.write(msg + u'\n')

for s in sys.argv[1:]:
    print query_authoritative_ns (s, log)

【讨论】:

【参考方案5】:

这是我的尝试。它使用系统的标准 DNS 服务器来查找***域的根服务器并解析链上各种 DNS 服务器的名称,我认为这很合适,因为这些名称可能很少更改。

import dns
import dns.name
import dns.query
import dns.resolver

def get_authoritative_nameserver(domain, log=lambda msg: None):
    n = dns.name.from_text(domain)

    depth = 2
    default = dns.resolver.get_default_resolver()
    nameserver = default.nameservers[0]

    last = False
    while not last:
        s = n.split(depth)

        last = s[0].to_unicode() == u'@'
        sub = s[1]

        log('Looking up %s on %s' % (sub, nameserver))
        query = dns.message.make_query(sub, dns.rdatatype.NS)
        response = dns.query.udp(query, nameserver)

        rcode = response.rcode()
        if rcode != dns.rcode.NOERROR:
            if rcode == dns.rcode.NXDOMAIN:
                raise Exception('%s does not exist.' % sub)
            else:
                raise Exception('Error %s' % dns.rcode.to_text(rcode))

        rrset = None
        if len(response.authority) > 0:
            rrset = response.authority[0]
        else:
            rrset = response.answer[0]

        rr = rrset[0]
        if rr.rdtype == dns.rdatatype.SOA:
            log('Same server is authoritative for %s' % sub)
        else:
            authority = rr.target
            log('%s is authoritative for %s' % (authority, sub))
            nameserver = default.query(authority).rrset[0].to_text()

        depth += 1

    return nameserver


import sys

def log(msg):
    print msg

print get_authoritative_nameserver(sys.argv[1], log)

这是一些示例输出:

Looking up com. on 192.168.255.10
l.gtld-servers.net. is authoritative for com.
Looking up ***.com. on 192.41.162.30
ns1.p19.dynect.net. is authoritative for ***.com.
Looking up meta.***.com. on 208.78.70.19
Same server is authoritative for meta.***.com.
208.78.70.19

【讨论】:

以上是关于如何使用 dnspython 找到域的权威 DNS 服务器?的主要内容,如果未能解决你的问题,请参考以下文章

python -- DNS处理模块dnspython

DNS处理模块dnspython

通过python的dnspython模块实现DNS流量攻击

iptables及智能DNS

DNS处理模块dnspython

Python学习笔记-DNS处理模块dnspython