DNS Query Code in C with winsock

By | July 28, 2020

DNS Query

When we type a web url or domain in our browser a dns request is immediately send by our browser to a DNS server to get the IP address of that web address.

In winsock applications we achieve this by gethostbyname() and things are pretty simple. In this article we shall do this simple thing without the help of gethostbyname().

We shall be sending DNS queries and receive the reply and extract the ipv4 address of the specified hostname.

DNS packets

Before writing the code to perform dns query, its important to understand the structure of a dns packet.

RFC 1035 shows the structure of DNS message as follows:

+---------------------+
| Header              |
+---------------------+
| Question            | the question for the name server
+---------------------+
| Answer              | RRs answering the question
+---------------------+
| Authority           | RRs pointing toward an authority
+---------------------+
| Additional          | RRs holding additional information
+---------------------+

Pretty simple to understand that queries wont have the answer, authority and additional fields.Packets are of course UDP and DNS servers feel comfortable to operate on port 53. So the first thing is to send a query containing the hostname.

Next task is to receive the reply which is expected to contain the information we are expecting. DNS queries are used for a variety of purpose.

Apart from getting the ipv4 address of a host we also use DNS for getting the mail exchange/server of a specified domain and etc. All type of queries and response packets are build nearly on the same structure depicted above.

When a DNS server replies it sends the question as it is and along with that are a bunch of RR's or resource records. All RR's stand in a queue and certain fields of the header tell that how many of the RR's are answers, how many authority and how many additionals.

DNS Header

The following is the structure of the DNS header. When sending dns queries, we shall need to construct this header structure in our program.

+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                     ID                        |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR| Opcode    |AA|TC|RD|RA| Z      |  RCODE    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                   QDCOUNT                     |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                   ANCOUNT                     |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                   NSCOUNT                     |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                   ARCOUNT                     |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

Here is a detailed explanation of each of the fields in the header. Let go through each of the fields of the header:

ID - A 16 bit identifier assigned by the program that generated the query. This identifier is copied in the corresponding reply and can be used by the client to match up replies with queries. Generate a random 16 bit number for each request.

QR - A one bit field that specifies whether this message is a query (0), or a response (1). Obviously,
you should use 0 for your requests, and expect to see a 1 in the response you receive.

OPCODE - A four bit field that specifies kind of query in this message. Use 0 to indicate a standard query.

AA Authoritative Answer - this bit is only meaningful in responses, and specifies that the responding name server is an authority for the domain name in question section. You should use this bit to report whether or not the response you receive is authoritative.

TC TrunCation - specifies that this message was truncated. For this project, you must exit and return an error if you receive a response that is truncated.

RD Recursion Desired - this bit directs the name server to pursue the query recursively. You should use 1, representing that you desire recursion.

RA Recursion Available - this be is set or cleared in a response, and denotes whether recursive query support is available in the name server. Recursive query support is optional. You must exit and return an error if you receive a response that indicates the server does not support recursion.

Z Reserved for future use. Set this field to 0.

RCODE Response code - this 4 bit field is set as part of responses. The values have the following interpretation:

0 : No error condition

1 : Format error - The name server was unable to interpret the query.

2 : Server failure - The name server was unable to process this query due to a problem with the name server.

3 : Name Error - Meaningful only for responses from an authoritative name server, this code signifies that the domain name referenced in the query does not exist.

4 : Not Implemented - The name server does not support the requested kind of query.

5 : Refused - The name server refuses to perform the specified operation for policy reasons. You should set this field to 0, and should assert an error if you receive a response indicating an error condition. You should treat 3 differently, as this represents the case where a requested name doesn’t exist.

QDCOUNT - an unsigned 16 bit integer specifying the number of entries in the question section. Set this field to 1 to indicate one question.

ANCOUNT - an unsigned 16 bit integer specifying the number of resource records in the answer section. Set this field to 0 , indicating you are not providing any answers.

NSCOUNT - an unsigned 16 bit integer specifying the number of name server resource records in the authority records section. Set this field to 0, and should ignore any response entries in this section.

ARCOUNT - an unsigned 16 bit integer specifying the number of resource records in the additional records section. Set this field to 0, and should ignore any response entries in this section.

For further reading checkup the relevant RFC.

A DNS response packet typically looks like this:

Header + Query + RR + RR + RR + RR + RR + RR ...

There is one query, followed by multiple resource record sections. Each resource record has some information about the queried domain name.

A Query structure looks like this

+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                                               |
/                    QNAME                      /
/                                               /
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    QTYPE                      |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    QCLASS                     |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

Note : QNAME is a variable length field to fit the hostname
QCLASS should be 1 since we are on internet
QTYPE determines what you want to know ; ipv4 address , mx etc.

Resource Record(RR) field looks like this

+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                                               |
/                                               /
/                     NAME                      /
|                                               |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                     TYPE                      |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                     CLASS                     |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                     TTL                       |
|                                               |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                  RDLENGTH                     |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
/                     RDATA                     /
/                                               /
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

Note again: NAME and RDATA are variable length field

Type field tells how RDATA relates to NAME. e.g. if TYPE is 1 then RDATA contains the ipv4 address of the NAME.

That's all about the structures we need.

DNS Compression Scheme

1. In DNS query and responses www.google.com is represented as 3www6google3com0. That is a number followed by that many characters until a period or the end of string.

2. And there is a compression scheme followed which is like this -> if google.com were to occur 10 times in the packet then it will written as google.com for the first time and after that a pointer will be placed at every next occurence of google.com to the position offset of the beginning of the first occurrence.

The starting of the dns header being the offset 0. For example if www.google.com is written starting at a offset of say 12 and some where later ns.google.com is to be written then it will be written as ns.16 which means point to offset 16 (where g of google is).

To implement this pointer technique 2 bytes are used where the first 2 bits are 1 and the rest 14 bits are the offset. So for 16 as offset the number you would need is 1100000000000000 + 1000.

Code

The structure for DNS header

typedef struct
{
unsigned short id;       // identification number
unsigned char rd :1;     // recursion desired
unsigned char tc :1;     // truncated message
unsigned char aa :1;     // authoritive 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;      // its z! 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
} DNS_HEADER;

Structure for the query ( we wont keep the name in this structure since size
is variable)

In the above structure the rd , tc , aa and opcode are in reverse order. This is due to the difference in the endian-ness of local machine and network. Local machines are little endian but network format is big endian. So fields have to be reversed in groups of 8 bit or 1 byte. Have a closer look at this in the structure.

typedef struct
{
unsigned short qtype;
unsigned short qclass;
} QUESTION;

Resource Record

typedef struct
{
unsigned short type;
unsigned short _class;
unsigned int ttl;
unsigned short data_len;
} R_DATA;

Once again name and rdata have been kept out.
These two structures will help

typedef struct
{
unsigned char *name;
R_DATA *resource;
unsigned char *rdata;
} RES_RECORD;

typedef struct
{
unsigned char *name;
QUESTION *ques;
} QUERY;

The working will be like

SOCKET s=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); //UDP packet for DNS queries
RES_RECORD answers[20],auth[20],addit[20]; //the replies from the DNS server
sockaddr_in dest;

dest.sin_family=AF_INET;

dest.sin_port=htons(53);
dest.sin_addr.s_addr=inet_addr(dns_servers[0]); //use the first dns server

unsigned char buf[65536],*qname,*reader;
DNS_HEADER *dns = NULL;
QUESTION *qinfo = NULL;

//Set the DNS structure to standard queries
dns=(DNS_HEADER*)&buf;

//set up the header
dns->id = (unsigned short)htons(GetCurrentProcessId());
dns->qr = 0; //This is a query
dns->opcode = 0; //This is a standard query
dns->aa = 0; //Not Authoritative
dns->tc = 0; //This message is not truncated
dns->rd = 1; //Recursion Desired
dns->ra = 0; //Recursion not available! hey we dont have it (lol)
dns->z = 0;
dns->ad = 0;
dns->cd = 0;
dns->rcode = 0;
dns->q_count = htons(1); //we have only 1 question
dns->ans_count = 0;
dns->auth_count = 0;
dns->add_count = 0;

//point to the query portion
qname =(unsigned char*)&buf[sizeof(DNS_HEADER)];
ChangetoDnsNameFormat(qname,host);

qinfo =(QUESTION*)&buf[sizeof(DNS_HEADER) + (strlen((const char*)qname) + 1)];

//fill it
qinfo->qtype = htons(1); //we are requesting the ipv4 address
qinfo->qclass = htons(1); //its internet (lol)

sendto(s,(char*)buf,sizeof(DNS_HEADER) + (strlen((const char*)qname)+1) +
sizeof(QUESTION),0,(sockaddr*)&dest,sizeof(dest))==SOCKET_ERROR)
int i=sizeof(dest);
recvfrom (s,(char*)buf,65536,0,(sockaddr*)&dest,&i);
dns=(DNS_HEADER*)buf;

//move ahead of the dns header and the query field
reader=&buf[sizeof(DNS_HEADER) + (strlen((const char*)qname)+1) +
sizeof(QUESTION)];

printf("nThe response contains : ");
printf("n %d Questions.",ntohs(dns->q_count));
printf("n %d Answers.",ntohs(dns->ans_count));
printf("n %d Authoritative Servers.",ntohs(dns->auth_count));
printf("n %d Additional records.nn",ntohs(dns->add_count));

//reading answers
int stop=0;
for(i=0;i<ntohs(dns->ans_count);i++)
{
answers[i].name=ReadName(reader,buf,stop);
reader+=stop;
answers[i].resource=(R_DATA*)(reader);
reader+=sizeof(R_DATA);
if(ntohs(answers[i].resource->type)==1)
{
    answers[i].rdata=new unsigned char[ntohs(answers[i].resource->data_len)];
    for(int j=0;j<ntohs(answers[i].resource->data_len);j++)
        answers[i].rdata[j]=reader[j];
    answers[i].rdata[ntohs(answers[i].resource->data_len)]='';
    reader+=ntohs(answers[i].resource->data_len);
}
else
{
    answers[i].rdata=ReadName(reader,buf,stop);
    reader+=stop;
}
}

and so on....
The IP address in the rdata section will be as numbers which must be converted
to a string and then to the dotted format using inet_ntoa like this

sockaddr_in a;
long *p;
p=(long*)addit[i].rdata;
a.sin_addr.s_addr=(*p);
printf("has IPv4 address : %s",inet_ntoa(a.sin_addr));

Authority resource records and Additional resource records need to be read just like Answer RRs. ChangetoDnsNameFormat(qname,host) function will convert the normal www.google.com in host to 3www6google3com0 and store the result in qname.

The ReadName() functions reads a NAME for query and RR blocks keeping in mind the compression strategy.

Fetching the DNS servers from configuration

Another function RetrieveDnsServersFromRegistry() in dns.cpp retrieves the DNS server IP stored on the system. On Windows, DNS servers are stored in the registry.

The registry key named:

HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces

contains the subkeys for the interfaces and each may contain a field called nameserver whose value will have the dns server fed by either you or stored dynamically in case of dialup connections.

Multiple DNS IP addresses may be present in one nameserver entry and then they are separated by a comma or simply a space.

Full working program source code:

//DNS Query Program

//Header Files
#include "winsock2.h"
#include "windows.h"
#include "stdio.h"
#include "conio.h"

#pragma comment(lib,"ws2_32.lib") //Winsock Library

//List of DNS Servers registered on the system
char dns_servers[10][100];

//Type field of Query and Answer
#define T_A 1 /* host address */
#define T_NS 2 /* authoritative server */
#define T_CNAME 5 /* canonical name */
#define T_SOA 6 /* start of authority zone */
#define T_PTR 12 /* domain name pointer */
#define T_MX 15 /* mail routing information */

//Function Declarations
void ngethostbyname (unsigned char*);
void ChangetoDnsNameFormat (unsigned char*,unsigned char*);
unsigned char* ReadName (unsigned char*,unsigned char*,int*);
void RetrieveDnsServersFromRegistry(void);
unsigned char* PrepareDnsQueryPacket (unsigned char*);

//DNS header structure
struct DNS_HEADER
{
	unsigned short id; // identification number

	unsigned char rd :1; // recursion desired
	unsigned char tc :1; // truncated message
	unsigned char aa :1; // authoritive 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; // its z! 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
};

//Constant sized fields of query structure
struct QUESTION
{
	unsigned short qtype;
	unsigned short qclass;
};

//Constant sized fields of the 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)

//Pointers to resource record contents
struct RES_RECORD
{
	unsigned char *name;
	struct R_DATA *resource;
	unsigned char *rdata;
};

//Structure of a Query
typedef struct
{
	unsigned char *name;
	struct QUESTION *ques;
} QUERY;

int main() //do you know what is int main() ?
{
	unsigned char hostname[100];

	RetrieveDnsServersFromRegistry();
	
	WSADATA firstsock;
	printf("\nInitialising Winsock...");
	if (WSAStartup(MAKEWORD(2,2),&firstsock) != 0)
	{
		printf("Failed. Error Code : %d",WSAGetLastError());
		return 1;
	}
	printf("Initialised.");

	printf("\nEnter Hostname to Lookup : ");
	gets((char*)hostname);
	ngethostbyname(hostname);

	_getch();
	return 0;
}

void ngethostbyname(unsigned char *host)
{
	unsigned char buf[65536],*qname,*reader;
	int i , j , stop;

	SOCKET s;
	struct sockaddr_in a;

	struct RES_RECORD answers[20],auth[20],addit[20]; //the replies from the DNS server
	struct sockaddr_in dest;

	struct DNS_HEADER *dns = NULL;
	struct QUESTION *qinfo = NULL;

	s = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); //UDP packet for DNS queries

	//Configure the sockaddress structure with information of DNS server
	dest.sin_family=AF_INET;
	dest.sin_port=htons(53);
	//Set the dns server
	if(strlen(dns_servers[0]) > 0)
	{
		//Use the dns server found on system
		dest.sin_addr.s_addr=inet_addr(dns_servers[0]);
	}
	else
	{
		//Use the open dns servers - 208.67.222.222 and 208.67.220.220
		dest.sin_addr.s_addr=inet_addr("208.67.222.222");
	}

	//Set the DNS structure to standard queries
	dns = (struct DNS_HEADER *)&buf;

	dns->id = (unsigned short) htons(GetCurrentProcessId());
	dns->qr = 0; //This is a query
	dns->opcode = 0; //This is a standard query
	dns->aa = 0; //Not Authoritative
	dns->tc = 0; //This message is not truncated
	dns->rd = 1; //Recursion Desired
	dns->ra = 0; //Recursion not available! hey we dont have it (lol)
	dns->z = 0;
	dns->ad = 0;
	dns->cd = 0;
	dns->rcode = 0;
	dns->q_count = htons(1); //we have only 1 question
	dns->ans_count = 0;
	dns->auth_count = 0;
	dns->add_count = 0;

	//point to the query portion
	qname =(unsigned char*)&buf[sizeof(struct DNS_HEADER)];

	ChangetoDnsNameFormat(qname,host);
	qinfo =(struct QUESTION*)&buf[sizeof(struct DNS_HEADER) + (strlen((const char*)qname) + 1)]; //fill it

	qinfo->qtype = htons(1); //we are requesting the ipv4 address
	qinfo->qclass = htons(1); //its internet (lol)

	printf("\nSending Packet...");
	if(sendto(s,(char*)buf,sizeof(struct DNS_HEADER) + (strlen((const char*)qname)+1) + sizeof(struct QUESTION),0,(struct sockaddr*)&dest,sizeof(dest))==SOCKET_ERROR)
	{
		printf("%d error",WSAGetLastError());
	}
	printf("Sent");

	i=sizeof(dest);
	printf("\nReceiving answer...");
	if(recvfrom (s,(char*)buf,65536,0,(struct sockaddr*)&dest,&i)==SOCKET_ERROR)
	{
		printf("Failed. Error Code : %d",WSAGetLastError());
	}
	printf("Received.");

	dns=(struct DNS_HEADER*)buf;

	//move ahead of the dns header and the query field
	reader=&buf[sizeof(struct DNS_HEADER) + (strlen((const char*)qname)+1) + sizeof(struct QUESTION)];

	printf("\nThe response contains : ");
	printf("\n %d Questions.",ntohs(dns->q_count));
	printf("\n %d Answers.",ntohs(dns->ans_count));
	printf("\n %d Authoritative Servers.",ntohs(dns->auth_count));
	printf("\n %d Additional records.\n\n",ntohs(dns->add_count));

	//reading answers
	stop=0;

	for(i=0;i<ntohs(dns->ans_count);i++)
	{
		answers[i].name=ReadName(reader,buf,&stop);
		reader = reader + stop;

		answers[i].resource = (struct R_DATA*)(reader);
		reader = reader + sizeof(struct R_DATA);

		if(ntohs(answers[i].resource->type) == 1) //if its an ipv4 address
		{
			answers[i].rdata = (unsigned char*)malloc(ntohs(answers[i].resource->data_len));

			for(j=0 ; j<ntohs(answers[i].resource->data_len) ; j++)
			answers[i].rdata[j]=reader[j];

			answers[i].rdata[ntohs(answers[i].resource->data_len)] = '\0';

			reader = reader + ntohs(answers[i].resource->data_len);

		}
		else
		{
			answers[i].rdata = ReadName(reader,buf,&stop);
			reader = reader + stop;
		}

	}

	//read authorities
	for(i=0;i<ntohs(dns->auth_count);i++)
	{
		auth[i].name=ReadName(reader,buf,&stop);
		reader+=stop;

		auth[i].resource=(struct R_DATA*)(reader);
		reader+=sizeof(struct R_DATA);

		auth[i].rdata=ReadName(reader,buf,&stop);
		reader+=stop;
	}

	//read additional
	for(i=0;i<ntohs(dns->add_count);i++)
	{
		addit[i].name=ReadName(reader,buf,&stop);
		reader+=stop;

		addit[i].resource=(struct R_DATA*)(reader);
		reader+=sizeof(struct R_DATA);

		if(ntohs(addit[i].resource->type)==1)
		{
		addit[i].rdata = (unsigned char*)malloc(ntohs(addit[i].resource->data_len));
		for(j=0;j<ntohs(addit[i].resource->data_len);j++)
		addit[i].rdata[j]=reader[j];

		addit[i].rdata[ntohs(addit[i].resource->data_len)]='\0';
		reader+=ntohs(addit[i].resource->data_len);

	}
	else
	{
		addit[i].rdata=ReadName(reader,buf,&stop);
		reader+=stop;
	}
	}

	//print answers
	for(i=0;i<ntohs(dns->ans_count);i++)
	{
		//printf("\nAnswer : %d",i+1);
		printf("Name : %s ",answers[i].name);

		if(ntohs(answers[i].resource->type)==1) //IPv4 address
		{
			long *p;
			p=(long*)answers[i].rdata;
			a.sin_addr.s_addr=(*p); //working without ntohl
			printf("has IPv4 address : %s",inet_ntoa(a.sin_addr));
		}
		if(ntohs(answers[i].resource->type)==5) //Canonical name for an alias
		{
			printf("has alias name : %s",answers[i].rdata);
		}

		printf("\n");
	}

	//print authorities
	for(i=0;i<ntohs(dns->auth_count);i++)
	{
		//printf("\nAuthorities : %d",i+1);
		printf("Name : %s ",auth[i].name);
		if(ntohs(auth[i].resource->type)==2)
		{
			printf("has authoritative nameserver : %s",auth[i].rdata);
		}
		printf("\n");
	}

	//print additional resource records
	for(i=0;i<ntohs(dns->add_count);i++)
	{
		//printf("\nAdditional : %d",i+1);
		printf("Name : %s ",addit[i].name);
		if(ntohs(addit[i].resource->type)==1)
		{
			long *p;
			p=(long*)addit[i].rdata;
			a.sin_addr.s_addr=(*p); //working without ntohl
			printf("has IPv4 address : %s",inet_ntoa(a.sin_addr));
		}
		printf("\n");
	}

	return;
}

unsigned char* ReadName(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);

	name[0]='\0';

	//read the names in 3www6google3com format
	while(*reader!=0)
	{
		if(*reader>=192)
		{
			offset = (*reader)*256 + *(reader+1) - 49152; //49152 = 11000000 00000000 ;)
			reader = buffer + offset - 1;
			jumped = 1; //we have jumped to another location so counting wont go up!
		}
		else
		{
			name[p++]=*reader;
		}

		reader=reader+1;

		if(jumped==0) *count = *count + 1; //if we havent jumped to another location then we can count up
	}

	name[p]='\0'; //string complete
	if(jumped==1) 
	{
		*count = *count + 1; //number of steps we actually moved forward in the packet
	}

	//now convert 3www6google3com0 to www.google.com
	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;
}

//Retrieve the DNS servers from the registry
void RetrieveDnsServersFromRegistry()
{
	HKEY hkey=0;
	char name[256];
	
	char *path = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces";
	
	char *fullpath[256];
	unsigned long s=sizeof(name);
	int dns_count=0 , err , i , j;
	HKEY inter;
	unsigned long count;

	//Open the registry folder
	RegOpenKeyEx(HKEY_LOCAL_MACHINE , path , 0 , KEY_READ , &hkey );

	//how many interfaces
	RegQueryInfoKey(hkey, 0 , 0 , 0 , &count , 0 , 0 , 0 , 0 , 0 , 0 , 0 );

	for(i=0 ; i<count ; i++)
	{
		s=256;
		//Get the interface subkey name
		RegEnumKeyEx(hkey , i , (char*)name , &s , 0 , 0 , 0 , 0 );

		//Make the full path
		strcpy((char*)fullpath,path);
		strcat((char*)fullpath,"\\");
		strcat((char*)fullpath,name);

		//Open the full path name
		RegOpenKeyEx(HKEY_LOCAL_MACHINE , (const char*)fullpath , 0 , KEY_READ , &inter );

		//Extract the value in Nameserver field
		s=256;
		err=RegQueryValueEx(inter , "NameServer" , 0 , 0 , (unsigned char*)name , &s );

		if(err==ERROR_SUCCESS && strlen(name)>0) 
		{
			strcpy(dns_servers[dns_count++],name);
		}
	}

	for(i=0;i<dns_count;i++)
	{
		for(j=0;j<strlen(dns_servers[i]);j++)
		{
			if(dns_servers[i][j]==',' || dns_servers[i][j]==' ')
			{
				strcpy(dns_servers[dns_count++] , dns_servers[i]+j+1);
				dns_servers[i][j] = 0;
			}
		}
	}

	printf("\nThe following DNS Servers were found on your system...");
	for(i=0;i<dns_count;i++)
	{
	printf("\n%d) %s",i+1,dns_servers[i]);
	}
}

//this will convert www.google.com to 3www6google3com ;got it :)
void ChangetoDnsNameFormat(unsigned char* dns,unsigned char* host)
{
	int lock=0 , i;

	strcat((char*)host,".");

	for(i=0 ; i<(int)strlen((char*)host) ; i++)
	{
		if(host[i]=='.')
		{
			*dns++=i-lock;
			for(;lock<i;lock++)
			{
				*dns++=host[lock];
			}
			lock++; //or lock=i+1;
		}
	}
	*dns++='\0';
}

Output

Here is a sample output from the above program

The following DNS Servers were found on your system...
1) 208.67.222.222
2) 208.67.220.220
Initialising Winsock...Initialised.
Enter Hostname to Lookup : www.google.com

Sending Packet...Sent
Receiving answer...Received.
The response contains :
 1 Questions.
 6 Answers.
 4 Authoritative Servers.
 4 Additional records.

Name : www.google.com has alias name : www.l.google.com
Name : www.l.google.com has IPv4 address : 74.125.235.16
Name : www.l.google.com has IPv4 address : 74.125.235.17
Name : www.l.google.com has IPv4 address : 74.125.235.18
Name : www.l.google.com has IPv4 address : 74.125.235.19
Name : www.l.google.com has IPv4 address : 74.125.235.20
Name : google.com has authoritative nameserver : ns4.google.com
Name : google.com has authoritative nameserver : ns1.google.com
Name : google.com has authoritative nameserver : ns3.google.com
Name : google.com has authoritative nameserver : ns2.google.com
Name : ns1.google.com has IPv4 address : 216.239.32.10
Name : ns2.google.com has IPv4 address : 216.239.34.10
Name : ns3.google.com has IPv4 address : 216.239.36.10
Name : ns4.google.com has IPv4 address : 216.239.38.10

If you get error the error message : "Sending Packet…10049 error" then check if there are any DNS servers in your Network configuration and that the above program is able to list them like this :

The following DNS Servers were found on your system...
1) 208.67.222.222
2) 208.67.220.220

If no DNS servers are found then error 10049 will come.

Update

The code has not been fixed from the 10049 winsock error. If no dns servers are found then the open dns server 208.67.222.222 will be used.

Download Source
https://www.binarytides.com/blog_files/dns_query/dns_query.cpp

For Linux version of the code check :
https://www.binarytides.com/blog/dns-query-code-in-c-with-linux-sockets/

A user named "dfelippa" read this post on codeproject and suggested and made some improvements to the code which include :

1. Better memory management
2. Support for MX Query

He modified the code and put up the new code at:

http://www.infologika.com.br/public/dnsquery_main.cpp

Check it out!

About Silver Moon

A Tech Enthusiast, Blogger, Linux Fan and a Software Developer. Writes about Computer hardware, Linux and Open Source software and coding in Python, Php and Javascript. He can be reached at [email protected].

51 Comments

DNS Query Code in C with winsock
  1. devils

    I am looking to run this code in vs code but it always gives me an undefined __imp_ntohs and wont compile do you know how to fix this?

  2. Uri London

    Is it possible that in various places, you have an out of bound bug (and you were lucky not to get an access violation):
    For example, in line 212:

    answers[i].rdata = (unsigned char*)malloc(ntohs(answers[i].resource->data_len));
    for(j=0 ; jdata_len) ; j++)
    answers[i].rdata[j]=reader[j];
    answers[i].rdata[ntohs(answers[i].resource->data_len)] = ‘\0’;

    You are allocating a buffer with data_len length, but you access data_len + 1

  3. Swen Kooij

    Why do you attempt to support domain names with Unicode characters? (>= 192). Section 3.1 of RFC-1035 clearly states: “assuming an ASCII character set, and a high order zero bit”.

  4. Fourty

    I wrote a windows version independent function a while ago to retrieve the dns server(s), perhaps it would be useful to someone (m_slWinsockDNS is type CStringList):

    #include

    int CSMTPClient::DiscoverWinsockDNSServers()
    {
    int iCount = 0;
    GUID guid = SVCID_NAMESERVER_UDP;

    WSAQUERYSET qs = {0};
    qs.dwSize = sizeof(WSAQUERYSET);
    qs.dwNameSpace = NS_DNS;
    qs.lpServiceClassId = &guid;

    HANDLE hLookup;
    int nRet = WSALookupServiceBegin(&qs, LUP_RETURN_NAME|LUP_RETURN_ADDR, &hLookup);
    if(nRet == SOCKET_ERROR)
    {
    int iError = WSAGetLastError();
    ReportError(_T(“Error obtaining DNS Server information (%d) %s”),
    iError, GetWin32ErrorText(iError));
    return – 1;
    }

    // Loop through the services
    DWORD dwResultLen = 1024;
    unsigned char* pResultBuf = new unsigned char[dwResultLen];

    while(TRUE)
    {
    nRet = WSALookupServiceNext(hLookup,
    LUP_RETURN_NAME|LUP_RETURN_ADDR,
    &dwResultLen, (WSAQUERYSET*)pResultBuf);
    if(nRet == SOCKET_ERROR)
    {
    // Buffer too small?
    if(WSAGetLastError() == WSAEFAULT)
    {
    delete pResultBuf;
    pResultBuf = new unsigned char[dwResultLen];
    continue;
    }
    break;
    }

    WSAQUERYSET* pqs = (LPWSAQUERYSET)pResultBuf;
    CSADDR_INFO* pcsa = pqs->lpcsaBuffer;

    // Loop through the CSADDR_INFO array
    for(int x = 0; x dwNumberOfCsAddrs; x ++)
    {
    // Get string equivalent for address
    TCHAR strAddrBuf[256] = {_T(”)};
    DWORD dwLen = sizeof(strAddrBuf);
    nRet = WSAAddressToString(pcsa->RemoteAddr.lpSockaddr,
    pcsa->RemoteAddr.iSockaddrLength, NULL, strAddrBuf, &dwLen);
    if(nRet == SOCKET_ERROR)
    break;

    // Remove the port number (‘:#’)
    CString strResult = strAddrBuf;
    CString strIP = _T(“”);
    int i = strResult.Find(_T(‘:’));
    if(i == -1)
    strIP = strResult;
    else
    strIP = strResult.Left(i);

    // Check for the duplicates before adding it
    if(m_slWinsockDNS.Find(strIP) == NULL)
    {
    m_slWinsockDNS.AddTail(strIP);
    iCount ++;
    }
    pcsa++;
    }
    }

    // Clean up
    WSALookupServiceEnd(hLookup);
    delete pResultBuf;

    return iCount;
    }

  5. doicanhden

    [code language=”cpp”]
    /*
    * this will convert http://www.google.com
    * to 3www6google3com;
    * got it :)
    */
    void ChangetoDnsNameFormat(byte_t* szDns, byte_t* szHost)
    {
    int src = 0, des = 0, len = 0; // Some positions.
    szDns[len] = 0;
    while (szHost[src] != ‘\0’) {
    if (szHost[src] == ‘.’) {
    len += szDns[len] + 1;
    szDns[len] = 0;

    des = len;
    ++src; // Ignore ‘.’
    } else {
    szDns[++des] = szHost[src++];
    ++szDns[len];
    }
    }
    szDns[++des] = ‘\0’;
    }
    [/code]

    1. Nik

      I’ve found the answer.

      To do a reverse lookup (PTR) on an IP Address:
      1. First reverse the IP address (from say 1.23.45.201 to 201.45.23.1 and append “.in-addr.arpa” to it).
      Will now be 201.45.23.1.in-addr.arpa.
      2. Call the ChangetoDnsNameFormat() function on it.
      3. Add a case in the print answers section for 12 (T_PTR) and handle it the same as T_CNAME
      (i.e. look at the answers[i].rdata.c_str() variable).

      Happy days.

    1. brian

      This is because : 3www6google3com0
      look this 3www.7gooogle4gato3com0
      The number is the number of characteres after the point. You replace the point with the number of characteres of the text segments.
      Sorry for my english!
      Enjoy it.

  6. cristian_gog

    Hello.

    Nice code.
    The reversed search is not implemented I think and I can see some interpretation issues when running

    Can you fix that?
    all the best :)

  7. Razvan

    Hey, very nice article.I have a question for you , how do you modify the code to run on linux instead of windows, for example the WSADATA structures are not recognized in linux and a lot of others. With a simple compile in linux you get lots of this “…” was not declared in this scope.I would love if you could give me an answer.Thank you

  8. brian

    Hi my friend, i was looking for this information a long time. I wann say thank you for your great help. There´s no so many people with your knowledge who want to share it. Sorry for my english.

    Regards!

    Thanks from Argentina!.

  9. Rat's

    It is a DNS resolver and along with it i want to have a code for the DNS name server. Please provide me some tips for the same…

  10. bilal

    Hey, first please I need some help with ReadName(unsigned char* reader,unsigned char* buffer,int* count), I didn’t understand how it works, what are the count, jumped, …
    offset = (*reader)*256 + *(reader+1) – 49152; //49152 = 11000000 00000000
    what are we doing in this instruction.
    Second if I want to make search in a local txt file that contains the RRs in a well structured form, how can i achieve this?
    thank you in advance :)

    1. Binary Tides Post author

      Yes, the bits are in reverse order per byte. So for every 8 bits the structure has to be in reverse order.

      1. Adrian

        endianess rise some issues on PPC and those pragmas and “:” uses for defining no of bits for every member also create some problems.

  11. Vicky.

    really excellent tutorial regarding DNS client. Since I don’t find any tutorial regarding DNS, it is a Oasis ….

    Good job done!!!

  12. Leo

    Hi, I have a question on you. Can you tell me what is the format of response? Like your sendto query, you have header question and query. How about response from DNS? How can I find answers at which position or which bit?

  13. Genuis

    The following DNS Servers were found on your system…
    Initialising Winsock…Initialised.
    Enter Hostname to Lookup : http://www.google.com

    Sending Packet…10049 error

    ————————————————-

    The following DNS Servers were found on your system…
    Initialising Winsock…Initialised.
    Enter Hostname to Lookup : http://www.gmail.com

    Sending Packet…10049 error

    ————————————————-

    The following DNS Servers were found on your system…
    Initialising Winsock…Initialised.
    Enter Hostname to Lookup : google.com

    Sending Packet…10049 error

    ————————————————-

    I tried all the possibility, yet it does not work. any clue? Am running this code in XP.

    1. Binary Tides Post author

      Error 10049 occurs because no dns servers are found in the network settings.
      Now the code is fixed. It uses the open dns servers if no dns servers are found, hence the error 10049 should not come.

  14. Andrew Smith

    Well i got it working but did not work out where i went wrong.

    The results i’m finding using this new tool is quite interesting and my service provider BT Home is returnig lots of fake DNS results back which presents a security threat because the DNS servers are allowing the malaware servers to hide all over the place and from what i can work out the ISPs see corporations like double-click as being top customers and to hell with the little people like you and me.

    A quick and dirty way to test the IP returned in the byte array from the DNS server is to simply strip out the last 4 bytes and put the dots in and if you need to fake the reply then you will need to copy bytes 0,1 from the original request from the client.

    Did you know Firefox now sends off DNS requests for any HREF, Atags in the HTML ? Looks like a lot of people have moved over to DNS tracking in an effort to avoid anti-spam software.

  15. Andrew Smith

    Does anyone know how to send a DNS responce back to the browser ?

    I have some code that listens for UDP packets on port 53 and sends the message off to the DNS server and i get a valid responce back and simply want to pass the byte array back to the browser.

    i’ve tryed sending the data bask using the sockets remote address (EndPoint) as a UDP but the browser simply fires the request back off again and i’ve test the code using a normal socket and it works fine.

    should be a ten second job but i’ve been at it for days and all because BT Home is now sending out fake DNS results from it’s UK servers.

  16. Deepanshu

    Hi
    I wanted to use the code in a commercial product and would like your permission. The product is open source and licensed under BSD license.

  17. Aaron

    Just a note on the Linux code…unless you #include , this code segfaults…for some reason, inet_ntoa() returns a bogus int instead of a string. Other than that, it works great…Thanks!

    @@ron

  18. sevcio

    Hi there,
    Sory for my english :) Program run succeded, but IPs are not correct i think. I mean it is different than IPs given from windows ‘ping’ command in command prompt. Why is that?

    1. Binary Tides Post author

      IP address given by this program might be different from what ping shows. For example http://www.google.com has many ips.
      It is sufficient to check whether the ip shown by the program takes you to the correct website/domain or not.

  19. Alessandro I.

    Hi there,
    I am trying to get a string representation of the all message sent over the socket. I am not confident with C, how can I print it?

    Thanks for the code btw.

  20. mona

    Hi, i don’t understand why but all the printf’s text do not appear on the command prompt.. Why is that? :S

    Thanks in advance!

  21. Sanarita Holmes

    Hey i have a question. I run this code with visual c++ but it does not display the host’s ip as if it is stuck in a loop (the cursor keeps blinking at “receiving answer..”) I have tried many hosts simplest of all, http://www.google.com but the console never shows any result. Please help me with this problem…its really urgent. Thanx

  22. admin Post author

    @Nicolas Goles – Because of the little endian machine architecture. In memory the bits are in the reverse order of what is in the structure. So to make the memory representation look like RFC 1035 the structure variables should be in reverse order.

  23. Nicolas Goles

    Hey i got a question for you, why is the declaration of the DNS header like that, I mean all the variables that you asignated with the :bit ( unsigned char rd :1; ) are in the inverse order of the proposed in RFC1035

    1. Binary Tides Post author

      Yes, all the variables are bytewise reversed or reversed in groups of 8 bits. This is done because of the difference in the endianness of the client and the network. Client machines are generally little endian whereas the network is big endian.

      For further reading on endianness , check :

      http://en.wikipedia.org/wiki/Endianness

Leave a Reply

Your email address will not be published. Required fields are marked *