+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 456 of 541

๐Ÿ“˜ DNS: Domain Name Resolution

Master dns: domain name resolution in Python with practical examples, best practices, and real-world applications ๐Ÿš€

๐Ÿ’ŽAdvanced
25 min read

Prerequisites

  • Basic understanding of programming concepts ๐Ÿ“
  • Python installation (3.8+) ๐Ÿ
  • VS Code or preferred IDE ๐Ÿ’ป

What you'll learn

  • Understand the concept fundamentals ๐ŸŽฏ
  • Apply the concept in real projects ๐Ÿ—๏ธ
  • Debug common issues ๐Ÿ›
  • Write clean, Pythonic code โœจ

๐ŸŽฏ Introduction

Welcome to this exciting tutorial on DNS (Domain Name System) in Python! ๐ŸŽ‰ Have you ever wondered how your computer magically knows where to find google.com or github.com? Thatโ€™s DNS working behind the scenes!

In this guide, weโ€™ll explore how to perform DNS lookups, resolve domain names to IP addresses, and even build our own DNS tools using Python. Whether youโ€™re building network applications ๐ŸŒ, monitoring tools ๐Ÿ“Š, or just curious about how the internet works, understanding DNS in Python is a superpower youโ€™ll want to have!

By the end of this tutorial, youโ€™ll be resolving domains like a network wizard! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding DNS

๐Ÿค” What is DNS?

DNS is like the internetโ€™s phone book ๐Ÿ“–. Think of it as a massive directory that translates human-friendly domain names (like โ€œgithub.comโ€) into computer-friendly IP addresses (like โ€œ140.82.113.3โ€).

In Python terms, DNS is your gateway to network programming. It allows you to:

  • โœจ Convert domain names to IP addresses
  • ๐Ÿš€ Perform reverse lookups (IP to domain)
  • ๐Ÿ›ก๏ธ Query different types of DNS records
  • ๐Ÿ” Build network diagnostic tools

๐Ÿ’ก Why Use DNS in Python?

Hereโ€™s why developers love working with DNS:

  1. Network Automation ๐Ÿ”ง: Automate network tasks and monitoring
  2. Security Tools ๐Ÿ›ก๏ธ: Build security scanners and validators
  3. Service Discovery ๐Ÿ”: Find services on your network
  4. Troubleshooting ๐Ÿ›: Debug network connectivity issues

Real-world example: Imagine building a website monitor ๐Ÿ“Š. With DNS, you can check if domains are resolving correctly, detect DNS hijacking, or verify your services are accessible worldwide!

๐Ÿ”ง Basic Syntax and Usage

๐Ÿ“ Simple DNS Lookups

Letโ€™s start with Pythonโ€™s built-in socket library:

import socket

# ๐Ÿ‘‹ Hello, DNS!
domain = "github.com"
ip_address = socket.gethostbyname(domain)
print(f"๐ŸŒ {domain} resolves to {ip_address}")

# ๐ŸŽจ Multiple IPs for a domain
ips = socket.gethostbyname_ex("google.com")
print(f"๐Ÿ“ Google IPs: {ips[2]}")  # List of IPs

# ๐Ÿ”„ Reverse DNS lookup
hostname = socket.gethostbyaddr("8.8.8.8")
print(f"๐Ÿท๏ธ 8.8.8.8 belongs to: {hostname[0]}")

๐ŸŽฏ Using the dnspython Library

For more power, letโ€™s use dnspython:

# First install: pip install dnspython
import dns.resolver

# ๐Ÿ—๏ธ Create a resolver
resolver = dns.resolver.Resolver()

# ๐ŸŽจ Query A records (IPv4 addresses)
result = resolver.resolve("github.com", "A")
for ip in result:
    print(f"โœจ A Record: {ip}")

# ๐Ÿš€ Query different record types
mx_records = resolver.resolve("gmail.com", "MX")
for mx in mx_records:
    print(f"๐Ÿ“ง Mail Server: {mx.preference} {mx.exchange}")

# ๐Ÿ›ก๏ธ Query TXT records (often used for verification)
txt_records = resolver.resolve("github.com", "TXT")
for txt in txt_records:
    print(f"๐Ÿ“ TXT: {txt}")

๐Ÿ’ก Practical Examples

๐Ÿ›’ Example 1: Domain Health Checker

Letโ€™s build a tool to check if domains are healthy:

import dns.resolver
import socket
from datetime import datetime

class DomainHealthChecker:
    def __init__(self):
        self.resolver = dns.resolver.Resolver()
        self.results = []
    
    # ๐Ÿฅ Check domain health
    def check_domain(self, domain):
        health_report = {
            "domain": domain,
            "timestamp": datetime.now(),
            "status": "๐ŸŸข Healthy",
            "issues": []
        }
        
        # ๐Ÿ” Check A records
        try:
            a_records = self.resolver.resolve(domain, "A")
            health_report["ip_count"] = len(a_records)
            health_report["ips"] = [str(ip) for ip in a_records]
            print(f"โœ… {domain} has {len(a_records)} IP addresses")
        except Exception as e:
            health_report["status"] = "๐Ÿ”ด Failed"
            health_report["issues"].append(f"โŒ No A records: {e}")
        
        # ๐Ÿ“ง Check MX records
        try:
            mx_records = self.resolver.resolve(domain, "MX")
            health_report["mail_servers"] = len(mx_records)
            print(f"๐Ÿ“ง {domain} has {len(mx_records)} mail servers")
        except:
            health_report["issues"].append("โš ๏ธ No mail servers")
        
        # ๐Ÿ›ก๏ธ Check nameservers
        try:
            ns_records = self.resolver.resolve(domain, "NS")
            health_report["nameservers"] = len(ns_records)
            print(f"๐ŸŒ {domain} has {len(ns_records)} nameservers")
        except:
            health_report["issues"].append("โš ๏ธ No NS records")
        
        self.results.append(health_report)
        return health_report
    
    # ๐Ÿ“Š Generate report
    def generate_report(self):
        print("\n๐Ÿฅ Domain Health Report")
        print("=" * 50)
        for result in self.results:
            print(f"\n๐ŸŒ Domain: {result['domain']}")
            print(f"๐Ÿ“… Checked: {result['timestamp'].strftime('%Y-%m-%d %H:%M:%S')}")
            print(f"Status: {result['status']}")
            if result.get('ips'):
                print(f"๐Ÿ”ข IPs: {', '.join(result['ips'])}")
            if result['issues']:
                print("โš ๏ธ Issues found:")
                for issue in result['issues']:
                    print(f"  {issue}")

# ๐ŸŽฎ Let's use it!
checker = DomainHealthChecker()
checker.check_domain("github.com")
checker.check_domain("google.com")
checker.check_domain("example.com")
checker.generate_report()

๐ŸŽฎ Example 2: DNS Speed Tester

Letโ€™s build a fun DNS performance tester:

import time
import dns.resolver
import statistics

class DNSSpeedTester:
    def __init__(self):
        self.dns_servers = {
            "๐ŸŒ Google": ["8.8.8.8", "8.8.4.4"],
            "โ˜๏ธ Cloudflare": ["1.1.1.1", "1.0.0.1"],
            "๐Ÿ›ก๏ธ OpenDNS": ["208.67.222.222", "208.67.220.220"],
            "๐Ÿข Quad9": ["9.9.9.9", "149.112.112.112"]
        }
        self.test_domains = [
            "google.com",
            "github.com", 
            "stackoverflow.com",
            "python.org"
        ]
    
    # โฑ๏ธ Test DNS speed
    def test_dns_server(self, server_ip, domain):
        resolver = dns.resolver.Resolver()
        resolver.nameservers = [server_ip]
        
        start_time = time.time()
        try:
            resolver.resolve(domain, "A")
            response_time = (time.time() - start_time) * 1000  # Convert to ms
            return response_time
        except:
            return None
    
    # ๐Ÿƒโ€โ™‚๏ธ Run speed test
    def run_speed_test(self):
        results = {}
        
        print("๐Ÿ DNS Speed Test Starting!")
        print("=" * 50)
        
        for provider, servers in self.dns_servers.items():
            print(f"\n๐Ÿ” Testing {provider}...")
            provider_times = []
            
            for server in servers:
                server_times = []
                for domain in self.test_domains:
                    response_time = self.test_dns_server(server, domain)
                    if response_time:
                        server_times.append(response_time)
                        print(f"  โœ… {server} โ†’ {domain}: {response_time:.2f}ms")
                    else:
                        print(f"  โŒ {server} โ†’ {domain}: Failed")
                
                if server_times:
                    avg_time = statistics.mean(server_times)
                    provider_times.extend(server_times)
                    print(f"  ๐Ÿ“Š {server} average: {avg_time:.2f}ms")
            
            if provider_times:
                results[provider] = {
                    "average": statistics.mean(provider_times),
                    "min": min(provider_times),
                    "max": max(provider_times),
                    "median": statistics.median(provider_times)
                }
        
        # ๐Ÿ† Show results
        self.show_results(results)
    
    # ๐Ÿ† Display results
    def show_results(self, results):
        print("\n๐Ÿ† DNS Speed Test Results")
        print("=" * 50)
        
        # Sort by average speed
        sorted_results = sorted(results.items(), key=lambda x: x[1]["average"])
        
        for i, (provider, stats) in enumerate(sorted_results):
            medal = "๐Ÿฅ‡" if i == 0 else "๐Ÿฅˆ" if i == 1 else "๐Ÿฅ‰" if i == 2 else "๐Ÿ…"
            print(f"\n{medal} {provider}")
            print(f"  โšก Average: {stats['average']:.2f}ms")
            print(f"  ๐Ÿš€ Fastest: {stats['min']:.2f}ms")
            print(f"  ๐ŸŒ Slowest: {stats['max']:.2f}ms")
            print(f"  ๐Ÿ“Š Median: {stats['median']:.2f}ms")

# ๐ŸŽฎ Let's test!
tester = DNSSpeedTester()
tester.run_speed_test()

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Advanced DNS Queries

When youโ€™re ready to level up, try these advanced techniques:

import dns.resolver
import dns.query
import dns.zone

# ๐ŸŽฏ Custom resolver with timeout
def advanced_resolver():
    resolver = dns.resolver.Resolver()
    resolver.timeout = 2.0  # 2 second timeout
    resolver.lifetime = 2.0
    
    # ๐ŸŒ Use specific DNS server
    resolver.nameservers = ['8.8.8.8']
    
    return resolver

# ๐Ÿ” Query all record types
def query_all_records(domain):
    record_types = ['A', 'AAAA', 'MX', 'TXT', 'NS', 'CNAME', 'SOA']
    resolver = advanced_resolver()
    
    print(f"๐Ÿ” Querying all records for {domain}")
    print("=" * 50)
    
    for record_type in record_types:
        try:
            answers = resolver.resolve(domain, record_type)
            print(f"\nโœจ {record_type} Records:")
            for rdata in answers:
                print(f"  ๐Ÿ“ {rdata}")
        except dns.resolver.NoAnswer:
            print(f"\nโš ๏ธ No {record_type} records found")
        except Exception as e:
            print(f"\nโŒ Error querying {record_type}: {e}")

# ๐Ÿš€ Asynchronous DNS queries
import asyncio
import aiodns

async def async_dns_lookup(domain):
    resolver = aiodns.DNSResolver()
    
    try:
        # ๐ŸŽจ Async A record lookup
        result = await resolver.gethostbyname(domain, socket.AF_INET)
        print(f"โœจ {domain} โ†’ {result.addresses}")
        
        # ๐Ÿ“ง Async MX lookup
        mx = await resolver.query(domain, 'MX')
        for record in mx:
            print(f"๐Ÿ“ง MX: {record.priority} {record.host}")
            
    except Exception as e:
        print(f"โŒ Error resolving {domain}: {e}")

# ๐Ÿƒโ€โ™‚๏ธ Run async lookups
async def run_async_lookups():
    domains = ["google.com", "github.com", "python.org"]
    tasks = [async_dns_lookup(domain) for domain in domains]
    await asyncio.gather(*tasks)

# asyncio.run(run_async_lookups())  # Uncomment to run

๐Ÿ—๏ธ Building a DNS Monitor

For the brave developers, hereโ€™s a DNS change monitor:

import time
import hashlib
from datetime import datetime

class DNSChangeMonitor:
    def __init__(self):
        self.resolver = dns.resolver.Resolver()
        self.baseline = {}
        self.changes = []
    
    # ๐Ÿ“ธ Take DNS snapshot
    def take_snapshot(self, domain):
        snapshot = {
            "timestamp": datetime.now(),
            "records": {}
        }
        
        record_types = ['A', 'MX', 'NS', 'TXT']
        
        for record_type in record_types:
            try:
                answers = self.resolver.resolve(domain, record_type)
                records = sorted([str(rdata) for rdata in answers])
                snapshot["records"][record_type] = records
                
                # Create hash for comparison
                record_hash = hashlib.md5(
                    ",".join(records).encode()
                ).hexdigest()
                snapshot["records"][f"{record_type}_hash"] = record_hash
                
            except:
                snapshot["records"][record_type] = []
                snapshot["records"][f"{record_type}_hash"] = ""
        
        return snapshot
    
    # ๐Ÿ” Monitor for changes
    def monitor_domain(self, domain, interval=60):
        print(f"๐Ÿ‘๏ธ Monitoring {domain} for DNS changes...")
        print(f"๐Ÿ”„ Checking every {interval} seconds")
        print("=" * 50)
        
        # Take initial snapshot
        self.baseline[domain] = self.take_snapshot(domain)
        print(f"๐Ÿ“ธ Initial snapshot taken at {datetime.now()}")
        
        while True:
            time.sleep(interval)
            current = self.take_snapshot(domain)
            
            # ๐Ÿ” Check for changes
            changes_found = False
            for record_type in ['A', 'MX', 'NS', 'TXT']:
                baseline_hash = self.baseline[domain]["records"].get(f"{record_type}_hash", "")
                current_hash = current["records"].get(f"{record_type}_hash", "")
                
                if baseline_hash != current_hash:
                    changes_found = True
                    change = {
                        "domain": domain,
                        "type": record_type,
                        "timestamp": datetime.now(),
                        "old": self.baseline[domain]["records"].get(record_type, []),
                        "new": current["records"].get(record_type, [])
                    }
                    self.changes.append(change)
                    
                    print(f"\n๐Ÿšจ Change detected in {record_type} records!")
                    print(f"๐Ÿ“… Time: {change['timestamp']}")
                    print(f"โŒ Old: {change['old']}")
                    print(f"โœ… New: {change['new']}")
            
            if changes_found:
                self.baseline[domain] = current
                print(f"๐Ÿ“ธ New baseline saved")
            else:
                print(f"โœ… No changes detected at {datetime.now().strftime('%H:%M:%S')}")

# ๐ŸŽฎ Usage example
# monitor = DNSChangeMonitor()
# monitor.monitor_domain("example.com", interval=30)

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Timeout Issues

# โŒ Wrong way - no timeout handling
def bad_dns_lookup(domain):
    return socket.gethostbyname(domain)  # ๐Ÿ’ฅ Can hang forever!

# โœ… Correct way - proper timeout
def good_dns_lookup(domain, timeout=5):
    resolver = dns.resolver.Resolver()
    resolver.timeout = timeout
    resolver.lifetime = timeout
    
    try:
        result = resolver.resolve(domain, 'A')
        return [str(ip) for ip in result]
    except dns.resolver.Timeout:
        print(f"โฑ๏ธ DNS lookup timed out after {timeout}s")
        return []
    except Exception as e:
        print(f"โŒ DNS error: {e}")
        return []

๐Ÿคฏ Pitfall 2: Not Handling NXDOMAIN

# โŒ Dangerous - assumes domain exists
def risky_lookup(domain):
    ips = socket.gethostbyname_ex(domain)[2]  # ๐Ÿ’ฅ KeyError if domain doesn't exist!
    return ips

# โœ… Safe - handle non-existent domains
def safe_lookup(domain):
    try:
        resolver = dns.resolver.Resolver()
        answers = resolver.resolve(domain, 'A')
        return [str(ip) for ip in answers]
    except dns.resolver.NXDOMAIN:
        print(f"๐Ÿšซ Domain {domain} does not exist")
        return []
    except dns.resolver.NoAnswer:
        print(f"โš ๏ธ No A records for {domain}")
        return []
    except Exception as e:
        print(f"โŒ Error: {e}")
        return []

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Use Proper Timeouts: Always set timeouts to avoid hanging
  2. ๐Ÿ“ Cache Results: DNS queries are expensive, cache when possible
  3. ๐Ÿ›ก๏ธ Handle All Exceptions: DNS can fail in many ways
  4. ๐ŸŽจ Use the Right Library: socket for simple, dnspython for complex
  5. โœจ Validate Input: Sanitize domain names before querying

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a DNS Diagnostic Tool

Create a comprehensive DNS diagnostic tool:

๐Ÿ“‹ Requirements:

  • โœ… Check if a domain is resolvable
  • ๐Ÿท๏ธ Show all DNS record types for a domain
  • ๐Ÿ“Š Compare DNS responses from multiple servers
  • โฑ๏ธ Measure DNS query performance
  • ๐ŸŽจ Pretty print the results with emojis!

๐Ÿš€ Bonus Points:

  • Add DNS cache analysis
  • Implement DNS-over-HTTPS queries
  • Create a DNS propagation checker

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
import dns.resolver
import socket
import time
from datetime import datetime
import concurrent.futures

class DNSDiagnosticTool:
    def __init__(self):
        self.dns_servers = {
            "Google": "8.8.8.8",
            "Cloudflare": "1.1.1.1",
            "OpenDNS": "208.67.222.222",
            "System Default": None
        }
        self.record_types = ['A', 'AAAA', 'MX', 'NS', 'TXT', 'CNAME', 'SOA']
    
    # ๐Ÿ” Comprehensive domain check
    def diagnose_domain(self, domain):
        print(f"\n๐Ÿฅ DNS Diagnostic Report for: {domain}")
        print("=" * 60)
        print(f"๐Ÿ“… Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        
        # 1๏ธโƒฃ Basic resolution check
        print("\n1๏ธโƒฃ Basic Resolution Check")
        self.check_basic_resolution(domain)
        
        # 2๏ธโƒฃ All record types
        print("\n2๏ธโƒฃ DNS Record Analysis")
        self.check_all_records(domain)
        
        # 3๏ธโƒฃ Multi-server comparison
        print("\n3๏ธโƒฃ Multi-Server DNS Comparison")
        self.compare_dns_servers(domain)
        
        # 4๏ธโƒฃ Performance metrics
        print("\n4๏ธโƒฃ DNS Performance Metrics")
        self.measure_performance(domain)
    
    # โœ… Basic resolution
    def check_basic_resolution(self, domain):
        try:
            ip = socket.gethostbyname(domain)
            print(f"  โœ… Domain resolves to: {ip}")
            
            # Reverse lookup
            try:
                hostname = socket.gethostbyaddr(ip)[0]
                print(f"  ๐Ÿ”„ Reverse DNS: {hostname}")
            except:
                print(f"  โš ๏ธ No reverse DNS configured")
                
        except socket.gaierror:
            print(f"  โŒ Domain does not resolve!")
    
    # ๐Ÿ“Š Check all records
    def check_all_records(self, domain):
        resolver = dns.resolver.Resolver()
        
        for record_type in self.record_types:
            try:
                answers = resolver.resolve(domain, record_type)
                print(f"\n  ๐Ÿ“ {record_type} Records ({len(answers)} found):")
                
                for i, rdata in enumerate(answers, 1):
                    if record_type == 'MX':
                        print(f"    {i}. Priority {rdata.preference}: {rdata.exchange}")
                    elif record_type == 'SOA':
                        print(f"    {i}. {rdata.mname} (Serial: {rdata.serial})")
                    else:
                        print(f"    {i}. {rdata}")
                        
            except dns.resolver.NoAnswer:
                print(f"\n  โš ๏ธ No {record_type} records")
            except Exception as e:
                print(f"\n  โŒ Error querying {record_type}: {str(e)[:50]}")
    
    # ๐ŸŒ Compare DNS servers
    def compare_dns_servers(self, domain):
        results = {}
        
        def query_server(name, server_ip):
            resolver = dns.resolver.Resolver()
            if server_ip:
                resolver.nameservers = [server_ip]
            
            try:
                start = time.time()
                answers = resolver.resolve(domain, 'A')
                elapsed = (time.time() - start) * 1000
                
                ips = sorted([str(ip) for ip in answers])
                return name, {
                    "ips": ips,
                    "time": elapsed,
                    "status": "โœ…"
                }
            except Exception as e:
                return name, {
                    "ips": [],
                    "time": 0,
                    "status": "โŒ",
                    "error": str(e)[:30]
                }
        
        # Query all servers in parallel
        with concurrent.futures.ThreadPoolExecutor() as executor:
            futures = [
                executor.submit(query_server, name, ip) 
                for name, ip in self.dns_servers.items()
            ]
            
            for future in concurrent.futures.as_completed(futures):
                name, result = future.result()
                results[name] = result
        
        # Display results
        for name, result in results.items():
            print(f"\n  ๐ŸŒ {name}:")
            print(f"    Status: {result['status']}")
            if result['ips']:
                print(f"    IPs: {', '.join(result['ips'])}")
                print(f"    Time: {result['time']:.2f}ms")
            elif 'error' in result:
                print(f"    Error: {result['error']}")
    
    # โฑ๏ธ Performance measurement
    def measure_performance(self, domain):
        resolver = dns.resolver.Resolver()
        times = []
        
        print(f"\n  ๐Ÿƒโ€โ™‚๏ธ Running 10 queries...")
        
        for i in range(10):
            start = time.time()
            try:
                resolver.resolve(domain, 'A')
                elapsed = (time.time() - start) * 1000
                times.append(elapsed)
                print(f"    Query {i+1}: {elapsed:.2f}ms")
            except:
                print(f"    Query {i+1}: Failed โŒ")
        
        if times:
            print(f"\n  ๐Ÿ“Š Performance Summary:")
            print(f"    โšก Fastest: {min(times):.2f}ms")
            print(f"    ๐ŸŒ Slowest: {max(times):.2f}ms")
            print(f"    ๐Ÿ“Š Average: {sum(times)/len(times):.2f}ms")
            
            # Performance rating
            avg = sum(times)/len(times)
            if avg < 50:
                print(f"    ๐Ÿ† Performance: Excellent!")
            elif avg < 100:
                print(f"    โœ… Performance: Good")
            elif avg < 200:
                print(f"    โš ๏ธ Performance: Fair")
            else:
                print(f"    โŒ Performance: Poor")

# ๐ŸŽฎ Test the tool!
diagnostic = DNSDiagnosticTool()
diagnostic.diagnose_domain("github.com")

๐ŸŽ“ Key Takeaways

Youโ€™ve mastered DNS in Python! Hereโ€™s what you can now do:

  • โœ… Perform DNS lookups with confidence ๐Ÿ’ช
  • โœ… Query different record types like a pro ๐Ÿ›ก๏ธ
  • โœ… Build DNS monitoring tools for real applications ๐ŸŽฏ
  • โœ… Handle DNS errors gracefully without crashes ๐Ÿ›
  • โœ… Optimize DNS performance in your applications! ๐Ÿš€

Remember: DNS is the backbone of the internet - now you can work with it like a network engineer! ๐Ÿค

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve conquered DNS in Python!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Build a DNS cache analyzer for your network
  2. ๐Ÿ—๏ธ Create a domain availability checker
  3. ๐Ÿ“š Explore DNS-over-HTTPS and DNS-over-TLS
  4. ๐ŸŒŸ Share your DNS tools with the community!

Remember: Every network expert started by understanding DNS. Keep exploring, keep building, and most importantly, have fun with networking! ๐Ÿš€


Happy DNS resolving! ๐ŸŽ‰๐ŸŒโœจ