安装dnspython
pip install dnspython
message.make_query构建DNS请求报文使用说明
dns.message.make_query(qname: ~dns.name.Name | str, rdtype: ~dns.rdatatype.RdataType | str, rdclass: ~dns.rdataclass.RdataClass | str = RdataClass.IN, use_edns: int | bool | None = None, want_dnssec: bool = False, ednsflags: int | None = None, payload: int | None = None, request_payload: int | None = None, options: ~typing.List[~dns.edns.Option] | None = None, idna_codec: ~dns.name.IDNACodec | None = None, id: int | None = None, flags: 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 相关参数(如 ednsflags
、payload
等)时才启用 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查询请求能够兼容更多的网络环境,并尽量避免由于数据包过大而导致的问题,设置payload
和request_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: Message, where: str, timeout: float | None = None, port: int = 53, source: str | None = None, source_port: int = 0, ignore_unexpected: bool = False, one_rr_per_rrset: bool = False, ignore_trailing: bool = False, raise_on_truncation: bool = False, sock: Any | None = None, ignore_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
, 可选) - 提供一个已有的非阻塞数据报套接字来发送查询。如果提供此参数,source
和source_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}")