C語言域名解析的簡單實現
本文轉自:http://basiccoder.com/dns-resolver-by-c.html
看了看DNS協議的相關東西,其實實際程式設計的時候根本用不到DNS細節的東西,要獲取域名的時候經終端下用host或者nslookup指令就可以,在c裡面使用gethostbyname或者getaddrinfo都能很輕鬆得將dns域名解析為ip地址,寫這個純粹出於個人興趣,或者說是閒得吧。
在進行域名解析的時候,解析程式向域名伺服器發起請求,域名伺服器也就是在作業系統網路配置的時候寫進去的那個DNS伺服器地址,或者也有可能是由ISP提供的自動獲取的,原理都一樣,域名伺服器收到請求後進行處理,首先在本地快取中查詢對應的域名,找到後將IP地址直接返回,找不到就向其它的授權伺服器請求資料,又可以分為著名的遞迴查詢和非遞迴查詢。
遞迴查詢就是說自始至終都由一臺域名伺服器進行查詢,它在自己這裡找不到的時候會向其它的域名伺服器請求並且獲取資料,然後返回給請求方。
非遞迴查詢是指域名伺服器收到請求後,如果自己有這個域名的資訊就返回,如果沒有就返回其它域名伺服器的指標,請求方再根據這些域名伺服器再發起查詢。
按自己的理解瞎扯了一通,也不知道準不準確,關於DNS的相關資料網上有的是,中文的都大批大批的。
DNS伺服器的原理其實沒什麼好說的,每天都在跟DNS打交道,但DNS的協議在實現上還是稍微有點意思的,本來想寫個程式來測試一個我所瞭解的DNS協議,後來在寫的時候還真發現一個小問題,DNS域名有時候會是一個主域名的別名,比如www.baidu.com,它就是www.a.shifen.com這個域名的別名,在DNS請求傳送過去之後,response裡面會有一個型別為CNAME的Answers項,裡面包含了主域名的相關資訊(其實也就是主域名的名稱和TTL),在這個應答訊息裡面可能會出現多個域名訊息,比如每個Answers的第一個欄位就是一個域名,當然為了減少資料包的容量,DNS系統對域名進行了壓縮,同一個域名只會出現一次,其它的時候再出現的話就會用一個DNS指標表示。
比如域名:www.baidu.com在資料包中的表示是 03 77 77 77 05 62 61 69 64 75 03 63 6f 6d 00
粗體的是長度,將域名中的點去掉,用長度來分隔域名,以0結束。DNS允許的長度為0-63個位元組,所以一個8位的長度最高兩位都為0。
而如果此處域名重複出現,信令中便會用DNS指標代替長度,指標為兩個位元組,16位的最位都為1,剩下的14位表示在在整個資料包中的偏移量,當程式讀取到c00c的時候很容易判斷它是一個指標而不是一個長度欄位,於是根據c00c指向的領移量,即從資料包開始後的第12個位元組,跳轉過去讀取出域名資訊。
/* * mytest.c * * Created on: Jan 22, 2016 * Author: allen */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <arpa/inet.h> #define DNS_SVR "202.96.199.133" #define DNS_HOST 0x01 #define DNS_CNAME 0x05 int socketfd; struct sockaddr_in dest; static void send_dns_request(const char *dns_name); static void parse_dns_response(); /** * Generate DNS question chunk */ static void generate_question(const char *dns_name , unsigned char *buf , int *len); /** * Check whether the current byte is * a dns pointer or a length */ static int is_pointer(int in); /** * Parse data chunk into dns name * @param chunk The complete response chunk * @param ptr The pointer points to data * @param out This will be filled with dns name * @param len This will be filled with the length of dns name */ static void parse_dns_name(unsigned char *chunk , unsigned char *ptr , char *out , int *len); int main(int argc , char *argv[]){ if(argc != 2){ printf("Usage : %s <domain name>\n" , argv[0]); exit(-1); } socketfd = socket(AF_INET , SOCK_DGRAM , 0); if(socketfd < 0){ perror("create socket failed"); exit(-1); } bzero(&dest , sizeof(dest)); dest.sin_family = AF_INET; dest.sin_port = htons(53); dest.sin_addr.s_addr = inet_addr(DNS_SVR); printf("%s\n",argv[1]); send_dns_request(argv[1]); parse_dns_response(); return 0; } static void parse_dns_response(){ unsigned char buf[1024]; unsigned char *ptr = buf; struct sockaddr_in addr; char *src_ip; int n , i , flag , querys , answers; int type , ttl , datalen , len; char cname[128] , aname[128] , ip[20] , *cname_ptr; unsigned char netip[4]; size_t addr_len = sizeof(struct sockaddr_in); n = recvfrom(socketfd , buf , sizeof(buf) , 0 , (struct sockaddr*)&addr , &addr_len); ptr += 4; /* move ptr to Questions */ querys = ntohs(*((unsigned short*)ptr)); ptr += 2; /* move ptr to Answer RRs */ answers = ntohs(*((unsigned short*)ptr)); ptr += 6; /* move ptr to Querys */ /* move over Querys */ for(i= 0 ; i < querys ; i ++){ for(;;){ flag = (int)ptr[0]; ptr += (flag + 1); if(flag == 0) break; } ptr += 4; } printf("-------------------------------\n"); /* now ptr points to Answers */ for(i = 0 ; i < answers ; i ++){ bzero(aname , sizeof(aname)); len = 0; parse_dns_name(buf , ptr , aname , &len); ptr += 2; /* move ptr to Type*/ type = htons(*((unsigned short*)ptr)); ptr += 4; /* move ptr to Time to live */ ttl = htonl(*((unsigned int*)ptr)); ptr += 4; /* move ptr to Data lenth */ datalen = ntohs(*((unsigned short*)ptr)); ptr += 2; /* move ptr to Data*/ if(type == DNS_CNAME){ bzero(cname , sizeof(cname)); len = 0; parse_dns_name(buf , ptr , cname , &len); printf("%s is an alias for %s\n" , aname , cname); ptr += datalen; } if(type == DNS_HOST){ bzero(ip , sizeof(ip)); if(datalen == 4){ memcpy(netip , ptr , datalen); inet_ntop(AF_INET , netip , ip , sizeof(struct sockaddr)); printf("%s has address %s\n" , aname , ip); printf("\tTime to live: %d minutes , %d seconds\n" , ttl / 60 , ttl % 60); } ptr += datalen; } } ptr += 2; } static void parse_dns_name(unsigned char *chunk , unsigned char *ptr , char *out , int *len){ int n , alen , flag; char *pos = out + (*len); for(;;){ flag = (int)ptr[0]; if(flag == 0) break; if(is_pointer(flag)){ n = (int)ptr[1]; ptr = chunk + n; parse_dns_name(chunk , ptr , out , len); break; }else{ ptr ++; memcpy(pos , ptr , flag); pos += flag; ptr += flag; *len += flag; if((int)ptr[0] != 0){ memcpy(pos , "." , 1); pos += 1; (*len) += 1; } } } } static int is_pointer(int in){ return ((in & 0xc0) == 0xc0); } static void send_dns_request(const char *dns_name){ unsigned char request[256]; unsigned char *ptr = request; unsigned char question[128]; int question_len; generate_question(dns_name , question , &question_len); *((unsigned short*)ptr) = htons(0xff00); ptr += 2; *((unsigned short*)ptr) = htons(0x0100); ptr += 2; *((unsigned short*)ptr) = htons(1); ptr += 2; *((unsigned short*)ptr) = 0; ptr += 2; *((unsigned short*)ptr) = 0; ptr += 2; *((unsigned short*)ptr) = 0; ptr += 2; memcpy(ptr , question , question_len); ptr += question_len; sendto(socketfd , request , question_len + 12 , 0 , (struct sockaddr*)&dest , sizeof(struct sockaddr)); } static void generate_question(const char *dns_name , unsigned char *buf , int *len){ char *pos; unsigned char *ptr; int n; *len = 0; ptr = buf; pos = (char*)dns_name; for(;;){ n = strlen(pos) - (strstr(pos , ".") ? strlen(strstr(pos , ".")) : 0); *ptr ++ = (unsigned char)n; memcpy(ptr , pos , n); *len += n + 1; ptr += n; if(!strstr(pos , ".")){ *ptr = (unsigned char)0; ptr ++; *len += 1; break; } pos += n + 1; } *((unsigned short*)ptr) = htons(1); *len += 2; ptr += 2; *((unsigned short*)ptr) = htons(1); *len += 2; }