The Internet's DNS protocol may be the least understood of all the major protocols that drive the Internet. A possible exception is the IP protocol itself (and the TCP protocol which aids in transport of IP packets), but many basic Internet tutorials contain a brief explanation of the concept of packets and how they travel through the network, with the result that even many end-users have some understanding of how IP works. Not so with DNS, which is black-boxed to an extreme: A computer uses DNS to resolve a computer's host name (like www.yahoo.com) to an IP address (like 50.100.150.200). And... That's it. End of explanation. While this is probably all the end-user needs to know, there is quite a bit more to DNS that is rarely studied or understood.
DNS is officially documented in RFCs 1034 and 1035, which collectively form STD0013 (standard 13). RFC 1034 is more conceptual in nature, however; RFC 1035 is the one you want to read if you're a programmer interested in actually understanding the format of DNS communications and programming DNS-using software. Of particular interest is RFC 1035's section 4: "Messages". This section defines each bit that makes up a DNS header, so you can put together DNS queries or responses one piece at a time.
Our first project with DNS will be to make a simple DNS server. In fact, we'll start with the simplest DNS server of all: One that doesn't work. Since our server isn't going to work, we won't need to worry about actually looking up anything. However, we're not going to make just any old DNS server that doesn't work; we're going to make one that fails properly. The DNS spec allows for a specific response code of "Server failure", indicating, obviously, that the server failed. Our first DNS server will actually be a program that simply always sends back this message. The complete program to do this looks like this:
#include <winsock.h> main() { WSADATA ws; WSAStartup(0x0101,&ws); SOCKET udp_socket; struct sockaddr_in peer; int peerlen; char recvbuffer[256]; int retval; char dnsreply[256]={0x0, 0x0, 0x84, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; peer.sin_family = AF_INET; peer.sin_port = htons(53); peer.sin_addr.s_addr = inet_addr("127.0.0.1"); udp_socket = socket(AF_INET, SOCK_DGRAM, 0); bind(udp_socket,(struct sockaddr *)&peer,sizeof(peer)); peerlen=sizeof(peer); //Now we enter an infinite loop where we keep trying to recvfrom. while(1) { retval = recvfrom(udp_socket, recvbuffer, sizeof(recvbuffer), 0, (struct sockaddr *)&peer, &peerlen); if (retval != -1) { dnsreply[0] = recvbuffer[0]; dnsreply[1] = recvbuffer[1]; sendto(udp_socket, dnsreply, 12, 0, (struct sockaddr *)&peer, sizeof(peer)); } } return 0; }
The key to this program is the line where "dnsreply" is assigned. You'll notice that when this variable is declared, the only non-zero values it contains are the third and fourth bytes. If you look at RFC 1035, you'll find that the values which we're setting with these bytes are as follows:
The value 84h (which is 10000100 in binary) states that this is a DNS reply, not a query (which is important, since otherwise a DNS client probably won't think the server is responding to it). It also turns on the "Authoritative Answer" bit, which makes the DNS client take it seriously.
The next byte is the value 2, which corresponds to response code 2, which, according to RFC 1035, is indeed the "Server failure" response code.
Try it! If you run this program and then run a DNS client using yourself as the DNS server, the DNS client should come back and say that the DNS server failed immediately. If this happens, you've programmed a DNS response! Now that we know how to listen on the UDP DNS port and send a properly-formatted DNS reply back, it shouldn't be that much harder to start constructing actual DNS resolutions.
Now that we know how to make a DNS server, we need to make a DNS client. Below is the C code for a complete program that will send a 12-byte DNS header to the IP address of your choosing. (As written here, it will send it to yourself, so you can test it out with the DNS "server" above. If you want to change this, just change the 127.0.0.1 address in the code.) The program below will then print out whatever response it got from the DNS server.
#include <winsock.h> #include <stdio.h> //for printf main() { WSADATA ws; WSAStartup(0x0101,&ws); SOCKET udp_socket; struct sockaddr_in peer; int peerlen; char recvbuffer[256]; int retval; char dnsquery[256]={0x61, 0x61, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; peer.sin_family = AF_INET; peer.sin_port = htons(53); peer.sin_addr.s_addr = inet_addr("127.0.0.1"); udp_socket = socket(AF_INET, SOCK_DGRAM, 0); peerlen=sizeof(peer); sendto(udp_socket, dnsquery, 12, 0, (struct sockaddr *)&peer, sizeof(peer)); do {retval = recvfrom(udp_socket, recvbuffer, sizeof(recvbuffer), 0, (struct sockaddr *)&peer, &peerlen);} while(retval == -1); printf("%s",recvbuffer); return 0; }
Now, if you just run it as it is, this program will probably not generate a response from a DNS server, because it contains zero DNS requests. The DNS server may decide not to respond at all to a DNS question with no requests in it. If this happens to you, what you need to do is expand the "dnsquery" variable so that it actually contains a DNS question after the header. For this extra data to be sent, you also need to increase the "12" in the sendto statement to however many bytes your dnsquery variable ends up being.
That should be all you need to do. Experiment with it to see what kinds of different queries and responses you can create. Have fun!