Skip to content

Instantly share code, notes, and snippets.

@NightSling
Created April 16, 2025 10:06
Show Gist options
  • Save NightSling/0a4903e129f11f8e3912701647782dac to your computer and use it in GitHub Desktop.
Save NightSling/0a4903e129f11f8e3912701647782dac to your computer and use it in GitHub Desktop.
DNS Query with C on Raw Linux Sockets with IPv6 Support
// 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;
}
@NightSling
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment