dnspython的基本使用

安装dnspython

pip install dnspython 

message.make_query构建DNS请求报文使用说明

 dns.message.make_query(qname: ~dns.name.Name | strrdtype: ~dns.rdatatype.RdataType | strrdclass: ~dns.rdataclass.RdataClass | str = RdataClass.INuse_edns: int | bool | None = Nonewant_dnssec: bool = Falseednsflags: int | None = Nonepayload: int | None = Nonerequest_payload: int | None = Noneoptions: ~typing.List[~dns.edns.Option] | None = Noneidna_codec: ~dns.name.IDNACodec | None = Noneid: int | None = Noneflags: int = <Flag.RD: 256>pad: int = 0)→ QueryMessage

dns.message.make_query ()   以下说明中"="号后的值为默认值。

qname

~dns.name.Name | str,  这是查询的域名,既可以是 dns.name.Name 对象,也可以是字符串。

示例:

import dns.message
import dns.name

# 使用字符串形式
qname_str = 'example.com'

# 使用 dns.name.Name 对象形式
qname_obj = dns.name.from_text('example.com')

rdtype

 ~dns.rdatatype.RdataType | str,    域名记录类型,可以是整数或者字符串

示例:

import dns.message
import dns.rdatatype

# 使用字符串形式
rdtype_str = 'A'

# 使用整数形式
rdtype_int = dns.rdatatype.A

rdclass

~dns.rdataclass.RdataClass | str = RdataClass.IN,    保持默认IN即可,目前主流查询类

use_edns

可在use_edns 设置关于edns的相关参数,建议这么设置,而不是直接在query中设置。

 int | bool | None = None,      默认值None,-1,即不启用。    

use_edns(

edns: int | bool | None = 0

ednsflags: int = 0

payload: int = 1232

request_payload: int | None = None

options: List[Option] | None = None,

pad: int = 0)→ None

指定要使用的 ends(扩展 DNS)级别,其类型可以是整数、布尔值或者 None。若为 None,只有在设置了其他 EDNS 相关参数(如 ednsflagspayload 等)时才启用 EDNS。不同的值含义如下:

  • 0:启用 EDNS版本0,目前版本0是主要流行版本,-1:不启用 EDNS0
  • True:启用,False不启用。

启用后,发送的请求报文中将多出以下部分

want_dnssec

bool = False, 布尔值,若为 True,则表示期望获取 DNSSEC(DNS 安全扩展)数据。

设置为True后,请求报文中以下将被置位:

want_dnssec与dns.flag.DO的区别。

  • 抽象层次want_dnssec 是一个更高级别的抽象,它让用户能够以简单的布尔值来表达是否需要 DNSSEC 数据,无需关心底层的 EDNS 标志设置。而 dns.flags.DO 是一个底层的 EDNS 标志,需要用户手动设置 EDNS 并指定该标志。

  • 兼容性want_dnssec 参数是 dnspython 为方便用户使用而提供的接口,在不同版本的 dnspython 中都能保持一致。dns.flags.DO 作为 EDNS 标准的一部分,在 DNS 协议层面是通用的,但手动设置 EDNS 标志可能需要更多的代码和对 EDNS 机制的理解。

        若你只是单纯希望获取 DNSSEC 数据,使用 want_dnssec = True 会更简单直接;若你需要对 EDNS 进行更细致的控制,或者需要同时设置多个 EDNS 标志,那么就可以手动设置 ednsflags 并包含 dns.flags.DO

ednsflags

 int | None = None

EDNS 标志值,是一个整数。 截至当前的标准和实践,除了DNSSEC OK(DO)位外,EDNS并没有为 ednsflags 定义其他官方使用的标志位。这意味着在实际应用中,ednsflags 主要是用来设置或取消DO位。尽管如此,EDNS标志字段保留了其他位以备将来使用,但这些位目前没有被分配特定的意义或功能。 

ednsflags=dns.flags.DO

payload(设置请求消息)

int | None = None, EDNS 发送方的有效负载字段,代表发送方能够处理的 UDP 数据报的最大大小,即此消息响应的最大允许大小。默认值为1232字节,通常情况下不需要对此选项进行设置。

import dns.message

# 设置最大 UDP 数据报大小为 1232 字节
payload_size = 1232

在使用EDNS(Extension Mechanisms for DNS,DNS扩展机制)时,常见的有效负载大小(payload size)是1232字节。这个值不是硬性规定,而是一个广泛接受的最佳实践值,旨在平衡网络分片和响应包大小。

选择1232字节作为EDNS的默认payload大小主要是考虑到以下因素:

  • 避免IP分片:以太网的MTU(最大传输单元)通常是1500字节。考虑到IP头部(通常为20字节)和UDP头部(8字节),留给DNS响应的有效数据空间大约是1472字节。但是,为了进一步确保不会发生IP分片,特别是当有VLAN标签(额外4字节)或其他网络层开销时,将DNS响应限制在1232字节是一个安全的选择。

  • 提高递归解析器和权威服务器之间的效率:通过保持响应大小在此范围内,可以减少因响应过大导致的重试或转换到TCP的情况,从而提高了查询的整体效率。

因此,如果你希望你的DNS查询请求能够兼容更多的网络环境,并尽量避免由于数据包过大而导致的问题,设置payloadrequest_payload为1232字节是个不错的选择。

request_payload(设置回复消息)

关联请求的EDNS payload大小。此字段在响应消息中是有意义的,如果设置为非零值,则会将响应的大小限制为指定的大小。默认值是0,这意味着“使用默认限制”,当前默认限制是65535

 int | None = None,

import dns.message

# 设置请求的有效负载大小
request_payload_size = 1500

 options

 ~typing.List[~dns.edns.Option] | None = None

dns.edns.NSID= OptionType.NSID获取响应服务器的名称或标识符。

dns.edns.DAU= OptionType.DAU客户端通知服务器其支持的DNSSEC算法列表

dns.edns.DHU= OptionType.DHU客户端通知服务器其支持的DS记录哈希算法列表。

dns.edns.N3U= OptionType.N3U客户端通知服务器其支持的NSEC3哈希算法列表。

dns.edns.ECS= OptionType.ECS提供客户端的子网信息以帮助服务器提供更精确的回答。

dns.edns.EXPIRE= OptionType.EXPIRE请求获取区域文件的过期时间。

dns.edns.COOKIE= OptionType.COOKIE提供一个安全机制来防止缓存中毒攻击。

dns.edns.KEEPALIVE= OptionType.KEEPALIVE 保持连接活跃状态,减少重新建立连接的时间。

dns.edns.PADDING= OptionType.PADDING添加填充数据以达到指定的UDP包大小,提高隐私性。

dns.edns.CHAIN= OptionType.CHAIN用于DNS查询链路跟踪。

dns.edns.Option 对象列表或者 None,表示 EDNS 选项。

import dns.message
import dns.edns

# 创建一个 EDNS 选项
    options=[                     # 添加ECS选项
        dns.edns.ECSOption('203.0.113.0', 24, 0)  # 客户端子网: 203.0.113.0/24, 作用域覆盖范围0
    ]

dns.edns.COOKIE设置cookei

query = dns.message.make_query('a.test.com', dns.rdatatype.A)

# 添加EDNS到查询
query.use_edns(
    edns=0,                       # 使用EDNS0版本
    ednsflags=dns.flags.DO,      # 设置DNSSEC OK位
    # payload=4096,                 # 设置较大的UDP负载大小
    options=[                     # 添加ECS选项
        dns.edns.ECSOption('203.0.113.0', 24, 0),  # 客户端子网: 203.0.113.0/24, 作用域覆盖范围0
        dns.edns.CookieOption(os.urandom(8),b'')    # 第一个参数是客户端cookie,第二个是服务器cookie,客户端cookie需要客户端指定,服务器cookie由服务器根据客户端ip、时间等参数生成。通常客户端第一次请求不携带服务器端cookie,后续请求填写服务器回复的cookie

    ],
    # request_payload=4096          # 请求的UDP负载大小
)


# 发送查询
response = dns.query.udp(query, '114.114.114.114')  # 使用Google DNS服务器

idna_codec

 ~dns.name.IDNACodec | None = None,

dns.name.IDNACodec 对象,用于指定 IDNA(国际化域名)的编码器 / 解码器。若为 None,则使用默认的 IDNA 2003 编码器 / 解码器。

 id

int | None = None

期望的查询 ID,类型为整数或者 None。若为 None,则会随机生成一个查询 ID。

flags

int = <Flag.RD: 256>,

期望的查询标志,是一个整数,默认值是 dns.flags.RD(递归查询标志)

1. dns.flags.QR(Query/Response Flag,查询 / 响应标志)

  • 32768(二进制为 1000 0000 0000 0000

  • 作用:此标志用于区分 DNS 消息是查询(Query)还是响应(Response)。若 QR 为 0,则表示这是一个查询消息;若 QR 为 1,则表示这是一个响应消息。

2. dns.flags.AA(Authoritative Answer Flag,权威回答标志)

  • 1024(二进制为 0000 0100 0000 0000

  • 作用:当 DNS 服务器是被查询域名的权威服务器时,会将该标志置为 1。也就是说,当 AA 为 1 时,表明响应消息中的答案是由权威服务器提供的,信息是准确可靠的。

3. dns.flags.TC(Truncation Flag,截断标志)

  • 512(二进制为 0000 0010 0000 0000

  • 作用:该标志用于指示 DNS 消息是否被截断。由于 UDP 有大小限制,若 DNS 响应消息过长,超过了 UDP 数据报的最大长度,就会被截断。此时,TC 标志会被置为 1。当客户端收到 TC 标志为 1 的消息时,通常会使用 TCP 协议重新发起查询,因为 TCP 没有类似 UDP 的大小限制。

4. dns.flags.RD(Recursion Desired Flag,递归查询标志)

  • 256(二进制为 0000 0001 0000 0000

  • 作用:客户端在发送查询消息时,若将 RD 标志置为 1,就表示希望 DNS 服务器进行递归查询。递归查询意味着 DNS 服务器会替客户端去查找最终的答案,而不是只返回部分信息或指向其他服务器的引用。

5. dns.flags.RA(Recursion Available Flag,递归可用标志)

  • 128(二进制为 0000 0000 1000 0000

  • 作用:此标志由 DNS 服务器在响应消息中设置。若 RA 为 1,表示该 DNS 服务器支持递归查询;若为 0,则表示不支持。

6. dns.flags.AD(Authentic Data Flag,认证数据标志)

  • 32(二进制为 0000 0000 0010 0000

  • 作用:该标志主要用于 DNSSEC(DNS Security Extensions,DNS 安全扩展)。当 AD 为 1 时,表明响应消息中的所有数据都已经过认证,是真实可靠的。

7. dns.flags.CD(Checking Disabled Flag,检查禁用标志)

  • 16(二进制为 0000 0000 0001 0000

  • 作用:此标志同样与 DNSSEC 相关。客户端在查询消息中设置 CD 为 1,表示希望 DNS 服务器在处理查询时禁用 DNSSEC 验证。

pad

int = 0

非负整数,若为 0(默认值),则不进行填充;否则会添加填充字节,使消息大小为 pad 的倍数。若 pad 不为 0,会向消息中添加 EDNS PADDING 选项。

message.make_query请求示例

基本域名解析请求

import dns.message
import dns.query
import dns.rdatatype
import dns.edns

# 创建一个新的DNS查询消息
query = dns.message.make_query('example.com', dns.rdatatype.A)

# 发送查询
response = dns.query.udp(query, '114.114.114.114')  # 使用Google DNS服务器

# 输出响应结果
print(response)

DNSSCE(表示客户端期望获取域名DNSSEC相关信息)

import dns.message
import dns.query
import dns.rdatatype
import dns.edns

# 创建一个新的DNS查询消息
query = dns.message.make_query('example.com', dns.rdatatype.A,want_dnssec=True)

# 发送查询
response = dns.query.udp(query, '114.114.114.114') 

# 输出响应结果
print(response)

传递ECS

import dns.message
import dns.query
import dns.rdatatype
import dns.edns

# 创建一个新的DNS查询消息
query = dns.message.make_query('example.com', dns.rdatatype.A)

# 添加EDNS到查询
query.use_edns(
    edns=0,                       # 使用EDNS0版本
    ednsflags=dns.flags.DO,      # 设置DNSSEC OK位
    payload=4096,                 # 设置较大的UDP负载大小
    options=[                     # 添加ECS选项
        dns.edns.ECSOption('203.0.113.0', 24, 0)  # 客户端子网: 203.0.113.0/24, 作用域覆盖范围0
    ],
    request_payload=4096          # 请求的UDP负载大小
)

# 发送查询
response = dns.query.udp(query, '8.8.8.8')  # 使用Google DNS服务器

# 输出响应结果
print(response)

dns.query.udp发送UDP DNS请求使用说明

dns.query.udp(q: Messagewhere: strtimeout: float | None = Noneport: int = 53source: str | None = Nonesource_port: int = 0ignore_unexpected: bool = Falseone_rr_per_rrset: bool = Falseignore_trailing: bool = Falseraise_on_truncation: bool = Falsesock: Any | None = Noneignore_errors: bool = False)

参数详解

  • q: (dns.message.Message) - 这是你想要发送的DNS查询消息。通常,你会使用 dns.message.make_query() 方法来创建这个对象。

  • where: (str) - 目标DNS服务器的IP地址(IPv4或IPv6)。这是你要发送查询的地方。

  • timeout: (float | None, 可选) - 等待响应的最大秒数。如果设置为 None(默认值),则表示无限期等待响应。如果你希望避免程序因为网络问题而卡住,建议设置一个合理的超时时间。

  • port: (int, 默认值: 53) - 目标DNS服务器监听的端口号。对于大多数DNS服务器,默认端口是53。

  • source: (str | None, 可选) - 发送查询的源地址。如果你不指定,则会使用通配符地址(即允许操作系统自动选择)。

  • source_port: (int, 默认值: 0) - 发送查询的源端口。默认值为0,意味着让操作系统自动选择一个可用的端口。

  • ignore_unexpected: (bool, 默认值: False) - 如果设置为True,将忽略来自未预期来源的响应。这对于调试或在某些特定情况下可能有用,但在生产环境中通常应保持为False。 接收到非预期来源的回复,会抛出异常。建议保持默认

  • one_rr_per_rrset: (bool, 默认值: False) - 如果设置为True,每个资源记录(RR)会被放入它自己的资源记录集(RRset)。这通常是不必要的,除非你有特殊需求。

  • ignore_trailing: (bool, 默认值: False) - 如果设置为True,将会忽略接收到的消息末尾的任何垃圾数据。这可以用于兼容某些不完全符合标准的DNS服务器。

  • raise_on_truncation: (bool, 默认值: False) - 如果设置为True,在截断标志(TC bit)被设置的情况下抛出异常。这可以帮助检测是否需要重试查询使用TCP。

  • sock: (socket.socket | None, 可选) - 提供一个已有的非阻塞数据报套接字来发送查询。如果提供此参数,sourcesource_port 参数将被忽略。

  • ignore_errors: (bool, 默认值: False) - 如果设置了各种格式错误或响应不匹配的情况发生时忽略它们,并继续监听有效的响应。这在开发阶段可能有用,但在生产环境中应谨慎使用。

返回值

该函数返回一个 dns.message.Message 对象,包含了从DNS服务器接收到的响应信息。

使用示例

import dns.message
import dns.query
import dns.name
import dns.rdatatype
import dns.edns

# 生成一个 DNS 查询消息
qname = 'www.test.com'  # 查询的域名
rdtype = dns.rdatatype.A  # 查询类型为 A 记录
query = dns.message.make_query(qname, rdtype)
query.use_edns(
    # edns=0,
    ednsflags=dns.flags.DO,
    options = [  # 添加ECS选项
              dns.edns.ECSOption('203.0.113.0', 24, 0)  # 客户端子网: 203.0.113.0/24, 作用域覆盖范围0
          ]
)

# 定义 DNS 服务器的 IP 地址(这里以 Google 的公共 DNS 服务器为例)
where = '114.114.114.114'
# 设置查询超时时间为 3 秒
timeout = 3
# 目标端口,使用默认的 53 端口
port = 53
# 不指定源地址,使用默认的通配符地址
source = None
# 不指定源端口,使用默认的 0,由系统自动分配
source_port = 0
# 不忽略来自意外源的响应
ignore_unexpected = False
# 不将每个资源记录(RR)放入其自己的资源记录集(RRset)
one_rr_per_rrset = False
# 不忽略接收到消息末尾的多余数据
ignore_trailing = False
# 如果响应消息的截断(TC)位被设置,抛出异常
raise_on_truncation = True
# 不使用自定义的套接字,让函数创建一个
sock = None
# 不忽略格式错误或响应不匹配等问题
ignore_errors = False

try:
    # 发送 UDP 查询并获取响应
    response = dns.query.udp(
        q=query,
        where=where,
        timeout=timeout,
        port=port,
        source=source,
        source_port=source_port,
        ignore_unexpected=ignore_unexpected,
        one_rr_per_rrset=one_rr_per_rrset,
        ignore_trailing=ignore_trailing,
        raise_on_truncation=raise_on_truncation,
        sock=sock,
        ignore_errors=ignore_errors
    )
    # 处理响应
    #打印响应码
    print(dns.rcode.to_text(response.rcode()))
    for rrset in response.answer:
        print(rrset.name)
        print(dns.rdatatype.to_text(rrset.rdtype))
        #打印响应ttl
        print(rrset.ttl)
        #打印返回的IP
        for rdata in rrset:
            print(rdata.address)
    else:
        print("没有查询到相关记录")
except Exception as e:
    print(f"查询过程中出现错误: {e}")

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值