Python: 如何区分不可用的DNS服务器和不存在的地址

2 投票
1 回答
2729 浏览
提问于 2025-04-18 14:03

名字解析可能会失败,原因是没有与主机名关联的IP地址,或者是因为无法连接到DNS服务器。很不幸的是,Python的 socket.create_connectionsocket.gethostbyname 这两个函数在这两种情况下似乎都会抛出相同的错误:

$ python3 -c 'import socket; socket.create_connection(("www.google.com_bar", 80))'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/usr/lib/python3.4/socket.py", line 491, in create_connection
    for res in getaddrinfo(host, port, 0, SOCK_STREAM):
  File "/usr/lib/python3.4/socket.py", line 530, in getaddrinfo
    for res in _socket.getaddrinfo(host, port, family, type, proto, flags):
socket.gaierror: [Errno -2] Name or service not known

$ python3 -c 'import socket; socket.gethostbyname("www.google_bar.com")'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
socket.gaierror: [Errno -5] No address associated with hostname

$ sudo vim /etc/resolv.conf # point to non-existing nameserver

$ python3 -c 'import socket; socket.create_connection(("www.google.com", 80))'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/usr/lib/python3.4/socket.py", line 491, in create_connection
    for res in getaddrinfo(host, port, 0, SOCK_STREAM):
  File "/usr/lib/python3.4/socket.py", line 530, in getaddrinfo
    for res in _socket.getaddrinfo(host, port, family, type, proto, flags):
socket.gaierror: [Errno -2] Name or service not known

$ python3 -c 'import socket; socket.gethostbyname("www.google.com")'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
socket.gaierror: [Errno -5] No address associated with hostname

有没有什么方法可以区分这两种情况,而不需要我再去查找一个“已知正常”的主机名呢?

这个解决方案应该能在Linux系统上使用。

1 个回答

1

你可以使用 dnslib 这个库来自己发送DNS请求。这个库的功能类似于dig命令,可以告诉你一个地址是否无法解析(NXDOMAIN),而不仅仅是解析失败(这通常会导致请求被阻塞 - 下面有补丁的说明)。

使用方法如下:

from dnslib import DNSRecord, RCODE

# I have dnsmasq running locally, so I can make requests to localhost.
# You need to find the address of the DNS server.
# The /etc/resolv.conf file is quite easily parsed, so you can just do that.
DNS_SERVER = "127.0.0.1"

query = DNSRecord.question("google.com")
response = DNSRecord.parse(query.send(DNS_SERVER, 53, False))
print RCODE[response.header.rcode]  # prints 'NOERROR'

query = DNSRecord.question("google.com_bar")
response = DNSRecord.parse(query.send(DNS_SERVER, 53, False))
print RCODE[response.header.rcode]  # prints 'NXDOMAIN'

# To avoid making the DNS request again when using the socket
# you can get the resolved IP address from the response.

问题出现在连接一个不存在的DNS服务器时。每次我尝试这样做时,请求都会挂起。(当我在命令行上使用像netcat这样的工具发送相同的请求时,请求也会挂起。我可能随机选择的IP不太好,或者遭遇了直接丢弃数据包的防火墙。)

不过,你可以修改源代码来添加超时设置。你可以在源代码中查看相关的方法,链接在 这里(在 github 上也有镜像)。我修改的部分是:

--- a/dns.py
+++ b/dns.py
@@ -357,6 +357,7 @@
             response = response[2:]
         else:
             sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
+            sock.settimeout(10)
             sock.sendto(self.pack(),(dest,port))
             response,server = sock.recvfrom(8192)
             sock.close()

这样做之后,DNS请求就会超时了。

撰写回答