Nikratio
Nikratio

Reputation: 2449

Python: how to distinguish unavailable DNS server from non-existing address

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

Answers (1)

Matthew Franglen
Matthew Franglen

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

Related Questions