Skip to content

guyycodes/mydns

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MyDNS - DNS Lookup Client Implementation Guide

Project Overview

This project implements a simplified DNS lookup client that performs iterative queries to resolve domain names. The client creates its own UDP socket and constructs/parses DNS packets according to RFC 1035.

Requirements

  • Language: C++ (mandatory)
  • Protocol: UDP
  • Port: 53 (standard DNS port)
  • No DNS libraries allowed - must implement raw packet construction/parsing

Command Usage

./mydns domain-name root-dns-ip

Example:

./mydns cs.fiu.edu 202.12.27.33

Implementation Steps (Baby Steps)

Step 1: Project Setup and Basic Structure

Goal: Create the basic C++ project structure with necessary headers.

  1. Create mydns.cpp as your main file
  2. Include necessary headers:
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <vector>
#include <string>
#include <sstream>
  1. Set up main function to parse command-line arguments:
int main(int argc, char* argv[]) {
    if (argc != 3) {
        std::cout << "Usage: mydns domain-name root-dns-ip" << std::endl;
        return 1;
    }
    
    std::string domain_name = argv[1];
    std::string root_dns_ip = argv[2];
    
    // Your DNS resolution logic here
    return 0;
}

Step 2: Create UDP Socket

Goal: Establish UDP socket for DNS communication.

int create_udp_socket() {
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        std::cerr << "Error creating socket" << std::endl;
        exit(1);
    }
    
    // Set socket timeout (optional but recommended)
    struct timeval tv;
    tv.tv_sec = 5;  // 5 second timeout
    tv.tv_usec = 0;
    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
    
    return sockfd;
}

Step 3: DNS Query Construction

Goal: Build a DNS query packet with proper header and question sections.

3.1: DNS Header Structure (12 bytes)

struct DNSHeader {
    uint16_t id;        // Transaction ID
    uint16_t flags;     // Flags (QR, Opcode, AA, TC, RD, RA, Z, RCODE)
    uint16_t qdcount;   // Number of questions
    uint16_t ancount;   // Number of answers
    uint16_t nscount;   // Number of authority records
    uint16_t arcount;   // Number of additional records
};

3.2: Convert Domain Name to DNS Format

Domain names in DNS use length-prefixed labels. Example: "cs.fiu.edu" becomes:

  • 2 (length) + "cs" + 3 (length) + "fiu" + 3 (length) + "edu" + 0 (terminator)
std::vector<uint8_t> encode_domain_name(const std::string& domain) {
    std::vector<uint8_t> encoded;
    std::stringstream ss(domain);
    std::string label;
    
    while (std::getline(ss, label, '.')) {
        encoded.push_back(label.length());
        for (char c : label) {
            encoded.push_back(c);
        }
    }
    encoded.push_back(0);  // Null terminator
    
    return encoded;
}

3.3: Create Complete Query

std::vector<uint8_t> create_dns_query(uint16_t id, const std::string& domain) {
    std::vector<uint8_t> query;
    
    // Header (12 bytes)
    // ID
    query.push_back(id >> 8);
    query.push_back(id & 0xFF);
    
    // Flags: Standard query with recursion desired (RD=1)
    query.push_back(0x01);  // QR=0, Opcode=0, AA=0, TC=0, RD=1
    query.push_back(0x00);  // RA=0, Z=0, RCODE=0
    
    // QDCOUNT = 1
    query.push_back(0x00);
    query.push_back(0x01);
    
    // ANCOUNT = 0
    query.push_back(0x00);
    query.push_back(0x00);
    
    // NSCOUNT = 0
    query.push_back(0x00);
    query.push_back(0x00);
    
    // ARCOUNT = 0
    query.push_back(0x00);
    query.push_back(0x00);
    
    // Question Section
    std::vector<uint8_t> qname = encode_domain_name(domain);
    query.insert(query.end(), qname.begin(), qname.end());
    
    // QTYPE = A (1)
    query.push_back(0x00);
    query.push_back(0x01);
    
    // QCLASS = IN (1)
    query.push_back(0x00);
    query.push_back(0x01);
    
    return query;
}

Step 4: Send Query and Receive Response

Goal: Send the query to DNS server and receive the response.

std::vector<uint8_t> send_dns_query(int sockfd, const std::string& server_ip, 
                                     const std::vector<uint8_t>& query) {
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(53);
    inet_pton(AF_INET, server_ip.c_str(), &server_addr.sin_addr);
    
    // Send query
    sendto(sockfd, query.data(), query.size(), 0,
           (struct sockaddr*)&server_addr, sizeof(server_addr));
    
    // Receive response
    std::vector<uint8_t> response(2048);
    socklen_t addr_len = sizeof(server_addr);
    int bytes_received = recvfrom(sockfd, response.data(), response.size(), 0,
                                  (struct sockaddr*)&server_addr, &addr_len);
    
    if (bytes_received < 0) {
        std::cerr << "Error receiving response" << std::endl;
        exit(1);
    }
    
    response.resize(bytes_received);
    return response;
}

Step 5: Parse DNS Response

Goal: Extract information from the DNS response packet.

5.1: Parse Header

struct ParsedHeader {
    uint16_t id;
    uint16_t flags;
    uint16_t qdcount;
    uint16_t ancount;
    uint16_t nscount;
    uint16_t arcount;
};

ParsedHeader parse_header(const std::vector<uint8_t>& response) {
    ParsedHeader header;
    header.id = (response[0] << 8) | response[1];
    header.flags = (response[2] << 8) | response[3];
    header.qdcount = (response[4] << 8) | response[5];
    header.ancount = (response[6] << 8) | response[7];
    header.nscount = (response[8] << 8) | response[9];
    header.arcount = (response[10] << 8) | response[11];
    return header;
}

5.2: Parse Domain Names (with compression support)

CRITICAL: DNS uses compression pointers (starting with 0xC0) to avoid repeating domain names.

std::string parse_domain_name(const std::vector<uint8_t>& response, 
                              size_t& offset) {
    std::string name;
    bool jumped = false;
    size_t jump_offset = 0;
    size_t max_jumps = 5;  // Prevent infinite loops
    size_t jumps = 0;
    
    while (offset < response.size() && jumps < max_jumps) {
        uint8_t len = response[offset];
        
        if (len == 0) {
            offset++;
            break;
        }
        
        // Check for compression pointer (11xxxxxx)
        if ((len & 0xC0) == 0xC0) {
            if (!jumped) {
                jump_offset = offset + 2;  // Save position after pointer
            }
            
            // Extract pointer offset
            uint16_t pointer = ((len & 0x3F) << 8) | response[offset + 1];
            offset = pointer;
            jumped = true;
            jumps++;
            continue;
        }
        
        // Regular label
        offset++;
        if (!name.empty()) {
            name += ".";
        }
        
        for (int i = 0; i < len; i++) {
            name += (char)response[offset++];
        }
    }
    
    if (jumped && jump_offset != 0) {
        offset = jump_offset;
    }
    
    return name;
}

5.3: Parse Resource Records

struct ResourceRecord {
    std::string name;
    uint16_t type;
    uint16_t rclass;
    uint32_t ttl;
    uint16_t rdlength;
    std::string rdata;  // For NS records
    std::string ip;      // For A records
};

ResourceRecord parse_resource_record(const std::vector<uint8_t>& response, 
                                     size_t& offset) {
    ResourceRecord rr;
    
    // Parse name
    rr.name = parse_domain_name(response, offset);
    
    // Parse type (2 bytes)
    rr.type = (response[offset] << 8) | response[offset + 1];
    offset += 2;
    
    // Parse class (2 bytes)
    rr.rclass = (response[offset] << 8) | response[offset + 1];
    offset += 2;
    
    // Parse TTL (4 bytes)
    rr.ttl = (response[offset] << 24) | (response[offset + 1] << 16) |
             (response[offset + 2] << 8) | response[offset + 3];
    offset += 4;
    
    // Parse RDLENGTH (2 bytes)
    rr.rdlength = (response[offset] << 8) | response[offset + 1];
    offset += 2;
    
    // Parse RDATA based on type
    if (rr.type == 1) {  // A record (IPv4 address)
        if (rr.rdlength == 4) {
            char ip_str[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &response[offset], ip_str, INET_ADDRSTRLEN);
            rr.ip = ip_str;
        }
        offset += rr.rdlength;
    } else if (rr.type == 2) {  // NS record (name server)
        size_t rdata_offset = offset;
        rr.rdata = parse_domain_name(response, rdata_offset);
        offset += rr.rdlength;
    } else {
        // Skip unknown record types
        offset += rr.rdlength;
    }
    
    return rr;
}

Step 6: Iterative Resolution Process

Goal: Implement the main resolution loop that queries servers iteratively.

void resolve_domain(const std::string& domain_name, const std::string& root_ip) {
    int sockfd = create_udp_socket();
    std::string current_server = root_ip;
    uint16_t query_id = 1;
    
    while (true) {
        std::cout << "----------------------------------------------------------------" << std::endl;
        std::cout << "DNS server to query: " << current_server << std::endl;
        
        // Create and send query
        std::vector<uint8_t> query = create_dns_query(query_id++, domain_name);
        std::vector<uint8_t> response = send_dns_query(sockfd, current_server, query);
        
        // Parse response
        ParsedHeader header = parse_header(response);
        
        std::cout << "Reply received. Content overview:" << std::endl;
        std::cout << header.ancount << " Answers." << std::endl;
        std::cout << header.nscount << " Intermediate Name Servers." << std::endl;
        std::cout << header.arcount << " Additional Information Records." << std::endl;
        
        size_t offset = 12;  // Skip header
        
        // Skip question section
        for (int i = 0; i < header.qdcount; i++) {
            parse_domain_name(response, offset);  // Skip QNAME
            offset += 4;  // Skip QTYPE and QCLASS
        }
        
        // Parse answer section
        std::cout << "Answers section:" << std::endl;
        std::vector<ResourceRecord> answers;
        for (int i = 0; i < header.ancount; i++) {
            ResourceRecord rr = parse_resource_record(response, offset);
            answers.push_back(rr);
            if (rr.type == 1) {  // A record
                std::cout << "Name : " << rr.name << " IP: " << rr.ip << std::endl;
            }
        }
        
        // If we have answers with IP addresses, we're done
        if (header.ancount > 0 && !answers.empty() && !answers[0].ip.empty()) {
            break;
        }
        
        // Parse authority section
        std::cout << "Authority Section:" << std::endl;
        std::vector<ResourceRecord> authority;
        for (int i = 0; i < header.nscount; i++) {
            ResourceRecord rr = parse_resource_record(response, offset);
            authority.push_back(rr);
            if (rr.type == 2) {  // NS record
                std::cout << "Name : " << rr.name << " Name Server: " << rr.rdata << std::endl;
            }
        }
        
        // Parse additional section
        std::cout << "Additional Information Section:" << std::endl;
        std::vector<ResourceRecord> additional;
        for (int i = 0; i < header.arcount; i++) {
            ResourceRecord rr = parse_resource_record(response, offset);
            additional.push_back(rr);
            if (rr.type == 1) {  // A record
                std::cout << "Name : " << rr.name << " IP : " << rr.ip << std::endl;
            }
        }
        
        // Find next server to query
        std::string next_server;
        for (const auto& auth : authority) {
            if (auth.type == 2) {  // NS record
                // Look for corresponding IP in additional section
                for (const auto& add : additional) {
                    if (add.type == 1 && add.name == auth.rdata) {
                        next_server = add.ip;
                        break;
                    }
                }
                if (!next_server.empty()) break;
            }
        }
        
        if (next_server.empty()) {
            std::cerr << "No intermediate server found!" << std::endl;
            break;
        }
        
        current_server = next_server;
    }
    
    close(sockfd);
}

Step 7: Complete Main Function

int main(int argc, char* argv[]) {
    if (argc != 3) {
        std::cout << "Usage: mydns domain-name root-dns-ip" << std::endl;
        return 1;
    }
    
    std::string domain_name = argv[1];
    std::string root_dns_ip = argv[2];
    
    resolve_domain(domain_name, root_dns_ip);
    
    return 0;
}

Compilation

g++ -o mydns mydns.cpp -std=c++11

Testing Tips

  1. Use Wireshark to capture and analyze your DNS packets:

    • Filter: dns
    • Compare your packets with real DNS queries
  2. Test with simple domains first:

    • Start with domains that require fewer iterations
    • Example: google.com typically requires fewer hops than cs.fiu.edu
  3. Common Issues to Watch For:

    • Byte order: Network byte order is big-endian
    • Compression pointers: Must handle 0xC0 prefixed pointers correctly
    • Timeout handling: DNS servers may not respond
    • Multiple IPs: Some servers return multiple IPs in additional section
  4. Debugging Output: Add verbose output to track:

    • Raw bytes sent/received
    • Parsed values at each step
    • Offset positions during parsing

Grading Criteria

  • Send query to root DNS server: 10%
  • Parse reply from root DNS server: 15%
  • Display server reply content: 15%
  • Extract intermediate DNS server IP: 15%
  • Send query to intermediate servers: 15%
  • Parse reply from intermediate servers: 15%
  • Display IPs for queried domain name: 15%

Important Notes

  1. Program name MUST be exactly mydns
  2. Must use C++ (not Python)
  3. Cannot use any DNS libraries
  4. Must create your own UDP socket
  5. Focus on IPv4 only (ignore IPv6)
  6. Only need to understand NS and A record types

DNS Message Format Reference

    +---------------------+
    |        Header       | 12 bytes
    +---------------------+
    |       Question      | Variable
    +---------------------+
    |        Answer       | Variable
    +---------------------+
    |      Authority      | Variable
    +---------------------+
    |     Additional      | Variable
    +---------------------+

Resources

About

A DNS resolution client implemented using C++

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors