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.
- Language: C++ (mandatory)
- Protocol: UDP
- Port: 53 (standard DNS port)
- No DNS libraries allowed - must implement raw packet construction/parsing
./mydns domain-name root-dns-ipExample:
./mydns cs.fiu.edu 202.12.27.33Goal: Create the basic C++ project structure with necessary headers.
- Create
mydns.cppas your main file - 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>- 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;
}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;
}Goal: Build a DNS query packet with proper header and question sections.
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
};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;
}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;
}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;
}Goal: Extract information from the DNS response packet.
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;
}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;
}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;
}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);
}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;
}g++ -o mydns mydns.cpp -std=c++11-
Use Wireshark to capture and analyze your DNS packets:
- Filter:
dns - Compare your packets with real DNS queries
- Filter:
-
Test with simple domains first:
- Start with domains that require fewer iterations
- Example:
google.comtypically requires fewer hops thancs.fiu.edu
-
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
-
Debugging Output: Add verbose output to track:
- Raw bytes sent/received
- Parsed values at each step
- Offset positions during parsing
- 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%
- Program name MUST be exactly
mydns - Must use C++ (not Python)
- Cannot use any DNS libraries
- Must create your own UDP socket
- Focus on IPv4 only (ignore IPv6)
- Only need to understand NS and A record types
+---------------------+
| Header | 12 bytes
+---------------------+
| Question | Variable
+---------------------+
| Answer | Variable
+---------------------+
| Authority | Variable
+---------------------+
| Additional | Variable
+---------------------+