Kodeclik Blog
How to send DNS requests with Python
When you goto a web browser and type in an address, say, https://www.mit.edu, and you get back the MIT homepage, you don’t think too much about it. But there are a lot of things happening behind the scenes to make it work for you! One of them is the DNS - or Domain Name Service.
Think of the internet like a giant city where every building (device or website) has a special street address - that's what we call an IP address. It looks like a set of numbers with dots between them, like 192.158.1.38. Just as mail carriers need your home address to deliver packages, computers need IP addresses to send information to the right place.
But remembering number addresses would be really hard for humans! Imagine remembering sets of four numbers for every address you need to keep track of! That's why we have DNS (Domain Name System), which works like a giant phone book for the internet. When you type in an easy-to-remember website name like "www.amazon.com," or “www.mit.edu”, the DNS translates it into the numbered IP address that computers understand. It's similar to how you might tell your friend to meet you at "McDonald's" instead of giving them the exact street address. Your friend knows what you mean, specifically which McDonald’s location you mean and how to get there.
So here’s what happens when you visit a website: When you type "www.mit.edu" in your web browser, your computer asks a DNS server (like an internet librarian) "What's the actual address for this website?" The DNS server looks up the numbered IP address and tells your computer where to go. This all happens in seconds, so you don't even notice it! Once your computer knows the IP address, it can find and show you the website you wanted to visit. The IP addresses themselves fall into two types as mentioned below.
IPv4 and IPv6
IPv4 and IPv6 are like two different versions of addresses for devices on the internet, with IPv6 being the newer, more advanced version.
IPv4 uses shorter, number-only addresses with dots (like 192.168.10.150), while IPv6 uses longer addresses with both letters and numbers separated by colons (like 2001:0000:3238:DFE1:0063:0000:0000:FEFB).
IPv4 can only support about 4.3 billion unique addresses, while IPv6 can support an astronomical number of addresses (340 undecillion). This is like upgrading from a small phone book for a town to one that could list every grain of sand on Earth. Wow!
IPv6 comes with built-in security features and is more efficient at routing data across the internet. It's like having a modern security system already installed in your house, while with IPv4, you need to add security features separately.
Sending DNS requests with Python dns library
Ok, now that we understand the distinction between website names and IP addresses, let us try to write a simple Python program that queries the DNS to find the IP address of www.mit.edu. For this, we use the Python DNS library which is specifically designed for DNS operations and provides a comprehensive DNS toolkit. Consider the below program:
The code defines a function called get_dns_records that uses the “dns” python library to perform DNS lookups for a specified domain. It starts by creating a resolver object, which is like a specialized tool for making DNS queries. Then, it attempts to find A records (IPv4 addresses) for the domain by using the resolve() method with the type parameter set to 'A'. When it finds these records, it prints each IPv4 address it discovers. If no A records are found, it catches the NoAnswer exception and prints a message saying no IPv4 records were found.
If we run the program, we will get the output:
What this tells us is that the computer hosting the www.mit.edu website has the physical address called “104.67.192.104”.
If you are so inclined you can take this information and try to determine more from it. For instance, you can do “IP geolocation” that can reveal quite detailed information about the location and origin of an IP address. You can determine the country with nearly 99% accuracy, the city/region with 80-90% accuracy, and even postal code or neighborhood (especially for static IPs).
Also you can update the program to fetch a different type of record. For instance, an “AAAA” record is used to fetch IPv6 records, or you can fetch a variety of different records using the program below.
Fetching multiple DNS records
Before you begin, you cannot fetch all DNS records at once using a wildcard query in Python's dns.resolver. This is a limitation of how DNS works, not just the Python implementation. While there is a record type called 'ANY', it's not reliable and is often blocked by modern DNS servers. Instead you can create a list of specific DNS record types and cycle through them as done below:
This program provides a comprehensive way to query multiple types of DNS records for a given domain using Python's dns.resolver library. It defines a function called get_common_records that takes a domain name as input and attempts to retrieve seven of the most commonly used DNS record types: A (IPv4 addresses), AAAA (IPv6 addresses), MX (mail exchange servers), TXT (text records), NS (nameservers), SOA (start of authority), and CNAME (canonical name). For each record type, it uses dns.resolver.resolve() to query the DNS system and prints any records it finds.
The program uses a try-except block for each record type to handle cases where records don't exist (NoAnswer exception) or when other errors occur during the lookup process. This makes the program robust and ensures it continues running even if some record types aren't available for the domain. When records are found, they are printed in a human-readable format using the to_text() method. This approach is particularly useful for system administrators or developers who need to quickly check multiple aspects of a domain's DNS configuration without running separate queries for each record type.
The output will be (note that your output might be different if MIT has changed how they have setup their website):
This DNS lookup output shows three different types of records for www.mit.edu. The A record shows that the domain resolves to the IPv4 address 104.67.192.104, which is the numerical address that computers use to locate and connect to MIT's web server1. The AAAA records contain two IPv6 addresses (starting with 2600:1402), which are the newer, longer format of IP addresses that provide more addressing space and better security features.
The CNAME (Canonical Name) record shows that www.mit.edu is actually an alias that points to www.mit.edu.edgekey.net. This suggests that MIT is using Akamai's Edge Key service for content delivery, as indicated by the edgekey.net domain. Using a CNAME record is a common practice that allows organizations to delegate their web traffic to content delivery networks (CDNs) while maintaining the ability to change the underlying infrastructure without modifying multiple DNS records.
Working with dynamic IP addresses
Sometimes when you use the above program on your own website for instance, you might notice different outputs each time. A home router or residential internet connection typically uses dynamic IP addresses, but we can't reliably predict which sites will show different IPs since this depends on the site's infrastructure and ISP settings. Most residential internet users connecting from home will see their own public IP address change periodically (usually every 24-36 hours) or when they restart their router. You can check this by visiting IP lookup services like ipinfo.io or whatismyip.com over different days or after router reboots.
For example, if you host a web server from your home connection, its IP address will change whenever your ISP assigns a new dynamic IP address to your connection. This is one reason why home users typically don't host websites directly and instead use hosting providers with static IPs. Static IP addresses are obviously beneficial because the address remains constant so there is no “lookup” needed every time somebody accesses your website and thus they will experience faster speeds.
Sending DNS requests with Python socket library
Now we will explore a different library, specifically the Python socket library. While it has some overlap with the Python dns library we saw before, the socket library provides a low-level interface for network communication, allowing developers to create both client and server applications. In fact, it is a powerful tool for network communication that enables devices to talk to each other over a network. It acts as a low-level interface for creating network connections, allowing programmers to establish both client and server applications that can send and receive data between computers.
Here's how to send DNS requests using Python's socket module:
This code defines a function called simple_dns_lookup that performs a DNS (Domain Name System) lookup for a given hostname. It uses Python's built-in socket module, which as mentioned above provides a low-level networking interface. The function takes a hostname (like "www.mit.edu", see later in the code) as input and attempts to resolve it to both IPv4 and IPv6 addresses using the getaddrinfo() method, which is a modern way to perform DNS resolution that works across different platforms.
Inside the function, the code processes the result returned by getaddrinfo(), which contains multiple pieces of information about each address found. It iterates through these results and sorts them into two lists: one for IPv4 addresses and another for IPv6 addresses. The sorting is done by checking the address family of each result - socket.AF_INET represents IPv4 addresses, while socket.AF_INET6 represents IPv6 addresses. Each address is extracted from the sockaddr tuple and added to the appropriate list.
When you use socket.getaddrinfo() or gethostbyname(), you're actually using the system's complete resolution chain, not just DNS lookups.
The function includes error handling through a try-except block that catches potential DNS resolution errors (socket.gaierror). If any error occurs during the lookup process, the function gracefully returns two empty lists instead of crashing. In the example usage, the code attempts to look up the IP addresses for "www.mit.edu" and prints out the IPv4 addresses found. This makes the function robust and suitable for production use where DNS lookups might fail due to network issues or invalid hostnames.
The output will be:
The output shows two lists of IP addresses for www.mit.edu. The first list contains IPv4 addresses, showing three identical entries of '104.67.192.104'. This repetition likely indicates multiple DNS records pointing to the same server, which is common for load balancing or redundancy purposes. This IP address is in the standard IPv4 format of four numbers separated by dots.
The second list shows IPv6 addresses, with six entries split between two unique addresses ('2600:1402:800:296::255e' and '2600:1402:800:28f::255e'), each repeated three times. These longer, hexadecimal-based addresses are in the modern IPv6 format, using colons as separators. Having multiple IPv6 addresses is also common for large institutions like MIT to ensure reliable access and distribute network traffic effectively.
Enjoy this blogpost? Want to learn Python with us? Sign up for 1:1 or small group classes.