Created
April 16, 2025 10:06
-
-
Save NightSling/0a4903e129f11f8e3912701647782dac to your computer and use it in GitHub Desktop.
DNS Query with C on Raw Linux Sockets with IPv6 Support
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// DNS Query on Raw Linux Socket | |
// | |
// Changelog: | |
// v2.0.0 - 2025-04-16 | |
// - Complete API refactoring for better developer experience | |
// - Added full IPv6 (AAAA record) support | |
// - Implemented proper error handling with descriptive error codes | |
// - Created user-provided buffer system for storing IP addresses | |
// - Added memory safety improvements and buffer overflow protections | |
// - Improved DNS server detection with fallbacks to public DNS | |
// - Reduced code bloat and improved maintainability | |
// - Fixed compression pointer handling in DNS responses | |
// - Added comprehensive documentation and examples | |
// | |
// MIT License | |
// | |
// Author: NightSling (https://daysling.com) (https://github.com/NightSling) | |
// | |
// Original by Silver Moon ([email protected]) | |
// | |
// | |
#include <arpa/inet.h> // inet_addr, inet_ntoa, inet_ntop | |
#include <errno.h> | |
#include <netinet/in.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <sys/socket.h> | |
#include <unistd.h> | |
#define DNS_TYPE_A 1 // IPv4 address | |
#define DNS_TYPE_AAAA 28 // IPv6 address | |
#define DNS_TYPE_NS 2 // Nameserver | |
#define DNS_TYPE_CNAME 5 // Canonical name | |
#define DNS_TYPE_SOA 6 // Start of authority zone | |
#define DNS_TYPE_PTR 12 // Domain name pointer | |
#define DNS_TYPE_MX 15 // Mail server | |
#define DNS_SUCCESS 0 | |
#define DNS_ERROR_SOCKET -1 | |
#define DNS_ERROR_SEND -2 | |
#define DNS_ERROR_RECEIVE -3 | |
#define DNS_ERROR_NO_RECORDS -4 | |
#define DNS_ERROR_BUFFER_SMALL -5 | |
#define DNS_ERROR_FILE_OPEN -6 | |
struct DNS_HEADER { | |
unsigned short id; // Identification number | |
unsigned char rd : 1; // Recursion desired | |
unsigned char tc : 1; // Truncated message | |
unsigned char aa : 1; // Authoritative answer | |
unsigned char opcode : 4; // Purpose of message | |
unsigned char qr : 1; // Query/response flag | |
unsigned char rcode : 4; // Response code | |
unsigned char cd : 1; // Checking disabled | |
unsigned char ad : 1; // Authenticated data | |
unsigned char z : 1; // Reserved | |
unsigned char ra : 1; // Recursion available | |
unsigned short q_count; // Number of question entries | |
unsigned short ans_count; // Number of answer entries | |
unsigned short auth_count; // Number of authority entries | |
unsigned short add_count; // Number of resource entries | |
}; | |
// Query structure | |
struct QUESTION { | |
unsigned short qtype; | |
unsigned short qclass; | |
}; | |
// Resource record structure | |
#pragma pack(push, 1) | |
struct R_DATA { | |
unsigned short type; | |
unsigned short _class; | |
unsigned int ttl; | |
unsigned short data_len; | |
}; | |
#pragma pack(pop) | |
static void change_to_dns_format(unsigned char *dns, const unsigned char *host); | |
static unsigned char *read_name(unsigned char *reader, unsigned char *buffer, | |
int *count); | |
static int get_dns_servers(char dns_servers[][100], int max_servers); | |
/** | |
* Query DNS for a host's IP address | |
* | |
* @param hostname The hostname to lookup | |
* @param query_type DNS_TYPE_A for IPv4 or DNS_TYPE_AAAA for IPv6 | |
* @param buffer User-provided buffer to store the result IP address | |
* @param buffer_size Size of the user-provided buffer | |
* @param output_size Will be set to the required buffer size | |
* | |
* @return DNS_SUCCESS on success, or a negative error code otherwise | |
*/ | |
int dns_query(const char *hostname, int query_type, void *buffer, | |
size_t buffer_size, size_t *output_size) { | |
if (!hostname || !buffer || !output_size) { | |
return DNS_ERROR_BUFFER_SMALL; | |
} | |
*output_size = | |
(query_type == DNS_TYPE_A) ? INET_ADDRSTRLEN : INET6_ADDRSTRLEN; | |
if (buffer_size < *output_size) { | |
return DNS_ERROR_BUFFER_SMALL; | |
} | |
unsigned char packet[4096]; | |
unsigned char *qname, *reader; | |
int i, stop, s; | |
struct sockaddr_in dest; | |
struct DNS_HEADER *dns = NULL; | |
struct QUESTION *qinfo = NULL; | |
char dns_servers[10][100]; | |
int dns_server_count = get_dns_servers(dns_servers, 10); | |
if (dns_server_count == 0) { | |
// Fallback to known public DNS servers | |
strcpy(dns_servers[0], "1.1.1.1"); // Cloudflare | |
strcpy(dns_servers[1], "8.8.8.8"); // Google | |
dns_server_count = 2; | |
} | |
// Create UDP socket | |
s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); | |
if (s < 0) { | |
return DNS_ERROR_SOCKET; | |
} | |
memset(&dest, 0, sizeof(dest)); | |
dest.sin_family = AF_INET; | |
dest.sin_port = htons(53); // DNS port | |
dest.sin_addr.s_addr = inet_addr(dns_servers[0]); | |
// Set up the DNS header | |
dns = (struct DNS_HEADER *)packet; | |
memset(dns, 0, sizeof(struct DNS_HEADER)); | |
dns->id = (unsigned short)htons(getpid()); | |
dns->qr = 0; // This is a query | |
dns->opcode = 0; // Standard query | |
dns->rd = 1; // Recursion desired | |
dns->q_count = htons(1); // 1 question | |
qname = (unsigned char *)&packet[sizeof(struct DNS_HEADER)]; | |
// Convert hostname to DNS format | |
unsigned char host_copy[256]; | |
strncpy((char *)host_copy, hostname, sizeof(host_copy) - 1); | |
host_copy[sizeof(host_copy) - 1] = '\0'; | |
change_to_dns_format(qname, host_copy); | |
qinfo = (struct QUESTION *)&packet[sizeof(struct DNS_HEADER) + | |
(strlen((const char *)qname) + 1)]; | |
qinfo->qtype = htons(query_type); | |
qinfo->qclass = htons(1); // Internet class | |
int packet_size = sizeof(struct DNS_HEADER) + | |
(strlen((const char *)qname) + 1) + sizeof(struct QUESTION); | |
if (sendto(s, (char *)packet, packet_size, 0, (struct sockaddr *)&dest, | |
sizeof(dest)) < 0) { | |
close(s); | |
return DNS_ERROR_SEND; | |
} | |
i = sizeof(dest); | |
memset(packet, 0, sizeof(packet)); | |
if (recvfrom(s, (char *)packet, sizeof(packet), 0, (struct sockaddr *)&dest, | |
(socklen_t *)&i) < 0) { | |
close(s); | |
return DNS_ERROR_RECEIVE; | |
} | |
close(s); | |
dns = (struct DNS_HEADER *)packet; | |
// Skip the header and question sections to get to the answer section | |
reader = &packet[sizeof(struct DNS_HEADER) + | |
(strlen((const char *)qname) + 1) + sizeof(struct QUESTION)]; | |
int ans_count = ntohs(dns->ans_count); | |
if (ans_count == 0) { | |
return DNS_ERROR_NO_RECORDS; | |
} | |
stop = 0; | |
for (i = 0; i < ans_count; i++) { | |
// Skip the name field | |
unsigned char *name = read_name(reader, packet, &stop); | |
free(name); | |
reader = reader + stop; | |
struct R_DATA *resource = (struct R_DATA *)reader; | |
reader = reader + sizeof(struct R_DATA); | |
if (ntohs(resource->type) == query_type) { | |
// IPv4 address | |
if (query_type == DNS_TYPE_A && ntohs(resource->data_len) == 4) { | |
char ip_str[INET_ADDRSTRLEN]; | |
struct in_addr ip_addr; | |
memcpy(&ip_addr, reader, 4); | |
inet_ntop(AF_INET, &ip_addr, ip_str, INET_ADDRSTRLEN); | |
strncpy(buffer, ip_str, buffer_size); | |
return DNS_SUCCESS; | |
} | |
// IPv6 address | |
else if (query_type == DNS_TYPE_AAAA && ntohs(resource->data_len) == 16) { | |
char ip_str[INET6_ADDRSTRLEN]; | |
struct in6_addr ip_addr; | |
memcpy(&ip_addr, reader, 16); | |
inet_ntop(AF_INET6, &ip_addr, ip_str, INET6_ADDRSTRLEN); | |
strncpy(buffer, ip_str, buffer_size); | |
return DNS_SUCCESS; | |
} | |
} | |
reader = reader + ntohs(resource->data_len); | |
} | |
return DNS_ERROR_NO_RECORDS; | |
} | |
/** | |
* Convert a hostname to DNS format | |
*/ | |
static void change_to_dns_format(unsigned char *dns, | |
const unsigned char *host) { | |
int lock = 0, i; | |
char host_copy[256]; | |
strncpy(host_copy, (char *)host, sizeof(host_copy) - 2); | |
host_copy[sizeof(host_copy) - 2] = '\0'; | |
strcat(host_copy, "."); | |
for (i = 0; i < strlen(host_copy); i++) { | |
if (host_copy[i] == '.') { | |
*dns++ = i - lock; | |
for (; lock < i; lock++) { | |
*dns++ = host_copy[lock]; | |
} | |
lock++; | |
} | |
} | |
*dns++ = '\0'; | |
} | |
/** | |
* Read a name from a DNS packet | |
*/ | |
static unsigned char *read_name(unsigned char *reader, unsigned char *buffer, | |
int *count) { | |
unsigned char *name; | |
unsigned int p = 0, jumped = 0, offset; | |
int i, j; | |
*count = 1; | |
name = (unsigned char *)malloc(256); | |
if (name == NULL) { | |
return NULL; | |
} | |
name[0] = '\0'; | |
while (*reader != 0) { | |
if (*reader >= 192) { // Compression used | |
offset = | |
(*reader) * 256 + *(reader + 1) - 49152; // 49152 = 11000000 00000000 | |
reader = buffer + offset - 1; | |
jumped = 1; | |
} else { | |
name[p++] = *reader; | |
} | |
reader = reader + 1; | |
if (jumped == 0) { | |
*count = *count + 1; | |
} | |
} | |
name[p] = '\0'; // Null terminator | |
if (jumped == 1) { | |
*count = *count + 1; | |
} | |
// Convert DNS format to dot format | |
for (i = 0; i < (int)strlen((const char *)name); i++) { | |
p = name[i]; | |
for (j = 0; j < (int)p; j++) { | |
name[i] = name[i + 1]; | |
i = i + 1; | |
} | |
name[i] = '.'; | |
} | |
name[i - 1] = '\0'; // Remove the last dot | |
return name; | |
} | |
/** | |
* Get DNS servers from resolv.conf | |
*/ | |
static int get_dns_servers(char dns_servers[][100], int max_servers) { | |
FILE *fp; | |
char line[200], *p; | |
int server_count = 0; | |
fp = fopen("/etc/resolv.conf", "r"); | |
if (fp == NULL) { | |
return 0; | |
} | |
while (fgets(line, 200, fp) && server_count < max_servers) { | |
if (line[0] == '#') { | |
continue; | |
} | |
if (strncmp(line, "nameserver", 10) == 0) { | |
p = strtok(line, " \t\n"); | |
p = strtok(NULL, " \t\n"); | |
if (p != NULL) { | |
strncpy(dns_servers[server_count], p, 99); | |
dns_servers[server_count][99] = '\0'; | |
server_count++; | |
} | |
} | |
} | |
fclose(fp); | |
return server_count; | |
} | |
/** | |
* Get error message for DNS query status code | |
*/ | |
const char *dns_strerror(int error_code) { | |
switch (error_code) { | |
case DNS_SUCCESS: | |
return "Success"; | |
case DNS_ERROR_SOCKET: | |
return "Failed to create socket"; | |
case DNS_ERROR_SEND: | |
return "Failed to send DNS query"; | |
case DNS_ERROR_RECEIVE: | |
return "Failed to receive DNS response"; | |
case DNS_ERROR_NO_RECORDS: | |
return "No matching DNS records found"; | |
case DNS_ERROR_BUFFER_SMALL: | |
return "Buffer too small for result"; | |
case DNS_ERROR_FILE_OPEN: | |
return "Failed to open resolv.conf"; | |
default: | |
return "Unknown error"; | |
} | |
} | |
// Example usage | |
int main(int argc, char *argv[]) { | |
char hostname[256]; | |
char ip_buffer[INET6_ADDRSTRLEN]; // Buffer large enough for IPv6 | |
size_t required_size; | |
int status; | |
// Get the hostname from the terminal | |
printf("Enter Hostname to Lookup: "); | |
if (scanf("%255s", hostname) != 1) { | |
printf("Failed to read hostname\n"); | |
return 1; | |
} | |
// Query for IPv4 address | |
printf("\n=== IPv4 Lookup ===\n"); | |
status = dns_query(hostname, DNS_TYPE_A, ip_buffer, sizeof(ip_buffer), | |
&required_size); | |
if (status == DNS_SUCCESS) { | |
printf("IPv4 Address: %s\n", ip_buffer); | |
} else { | |
printf("IPv4 Lookup Failed: %s\n", dns_strerror(status)); | |
} | |
// Query for IPv6 address | |
printf("\n=== IPv6 Lookup ===\n"); | |
status = dns_query(hostname, DNS_TYPE_AAAA, ip_buffer, sizeof(ip_buffer), | |
&required_size); | |
if (status == DNS_SUCCESS) { | |
printf("IPv6 Address: %s\n", ip_buffer); | |
} else { | |
printf("IPv6 Lookup Failed: %s\n", dns_strerror(status)); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Original Gist: https://gist.github.com/fffaraz/9d9170b57791c28ccda9255b48315168