Reputation: 2449
Name resolution may fail because there is no ip associated with the hostname, or because the DNS server cannot be reached. Unfortunately, Python's socket.create_connection
and socket.gethostbyname
functions seem to raise the same error in both situations:
$ 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
Is there any way to distinguish these two cases that does not require me to perform a second lookup for a "known-good" hostname?
The solution should work under Linux.
Upvotes: 2
Views: 2795
Reputation: 4532
You can use the dnslib library client to make the DNS request yourself. The client provides dig like functionality that can indicate if an address fails to resolve (NXDOMAIN
) compared to just failing to resolve (which unfortunately just blocks - see patch below).
You use it like so:
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.
The problem comes when making a connection to a non existant DNS Server. Every time I have tried this the request just hangs. (When I make the same requests on the command line, using something like netcat, the request also just hangs. I may be picking random IPs poorly and suffering from firewalls that just drop the packets)
Anyway you can alter the source code to add a timeout. You can view the relevant method in the source here (also mirrored on github). What I changed was:
--- 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()
After doing this the DNS request timed out.
Upvotes: 1