1關於DNS:
1.DNS是基於UDP實現的。
2.域名解析總體可分為兩大步驟,第一個步驟是本機向本地域名服務器發出一個DNS請求報文,報文里攜帶需要查詢的域名;第二個步驟是本地域名服務器向本機回應一個DNS響應報文,里面包含域名對應的IP地址。
2關於DNS報文:
如果要實現DNS,必須要理解透徹DNS報文的結構和功能!DNS報文結構如下:
DNS報文格式:
1首部:
1.1TransactionID(會話標識2byte)是DNS報文的ID標識,對於請求報文和其對應的應答報文,這個字段是相同的,通過它可以區分DNS應答報文是哪個請求的響應。
1.2Flags:
QR(1bit):查詢/響應標志,0為查詢,1為響應。
opcode(4bit):0表示標准查詢,1表示反向查詢,2表示服務器狀態請求。
AA(1bit):表示授權回答。
TC(1bit):表示可截斷的。
RD(1bit):表示期望遞歸。
RA(1bit):表示可用遞歸。
Z :保留值,暫時未使用。在所有的請求和應答報文中必須置為0。
rcode(4bit):
rcode:應答碼(Response code) ,這4個比特位在應答報文中設置,代表的含義如下:
0 沒有錯誤。
1 報文格式錯誤(Format error) - 服務器不能理解請求的報文。
2 服務器失敗(Server failure) - 因為服務器的原因導致沒辦法處理這個請求。
3 名字錯誤(Name Error) - 只有對授權域名解析服務器有意義,指出解析的域名不存在。
4 沒有實現(Not Implemented) - 域名服務器不支持查詢類型。
5 拒絕(Refused) - 服務器由於設置的策略拒絕給出應答。比如,服務器不希望對某些請求者給出應答,或者服務器不希望進行某些操作(比如區域傳送zone transfer)。
1.3數量字段(總共8字節):Questions、Answer RRs、Authority RRs、Additional RRs 各自表示后面的四個區域的數目。Questions表示查詢問題區域節的數量,Answers表示回答區域的數量,Authoritative namesversers表示授權區域的數量,Additional recoreds表示附加區域的數量。
2查詢問題區域中的報文格式:
2.1查詢名:長度不固定,且不使用填充字節,一般該字段表示的就是需要查詢的域名(如果是反向查詢,則為IP,反向查詢即由IP地址反查域名),一般的格式如下圖所示。
2.2查詢類型:每個問題有一個查詢類型。2個字節表示查詢類型,取值可以為任何可用的類型值,以及通配碼來表示所有的資源記錄。
類型 助記符 說明
1 A 由域名獲得IPv4地址
2 NS 查詢域名服務器
5 CNAME 查詢規范名稱
6 SOA 開始授權
11 WKS 熟知服務
12 PTR 把IP地址轉換成域名
13 HINFO 主機信息
15 MX 郵件交換
28 AAAA 由域名獲得IPv6地址
252 AXFR 傳送整個區的請求
255 ANY 對所有記錄的請求
2.3查詢類:通常為1,表明是Internet數據
3回答區域的報文格式:
該區域有三個,但格式都是一樣的。這三個區域分別是:回答區域,授權區域和附加區域。
3.1域名(2字節或不定長):它的格式和查詢區域的查詢名字字段是一樣的。有一點不同就是,當報文中域名重復出現的時候,該字段使用2個字節的偏移指針來表示。比如,在資源記錄中,域名通常是查詢問題部分的域名的重復,因此用2字節的指針來表示,具體格式是最前面的兩個高位是 11,用於識別指針。其余的14位從DNS報文的開始處計數(從0開始),指出該報文中的相應字節數。
3.2查詢類型:表明資源紀錄的類型。
3.3查詢類:對於Internet信息,總是IN(1)。
3.4生存時間(TTL):以秒為單位,表示的是資源記錄的生命周期,一般用於當地址解析程序取出資源記錄后決定保存及使用緩存數據的時間,它同時也可以表明該資源記錄的穩定程度,極為穩定的信息會被分配一個很大的值。
3.5資源數據:該字段是一個可變長字段,表示按照查詢段的要求返回的相關資源記錄的數據。可以是Address(表明查詢報文想要的回應是一個IP地址)或者CNAME(表明查詢報文想要的回應是一個規范主機名)等。
3算法流程:
4代碼及注釋:
#include<stdio.h> #include<string.h> #include<stdlib.h> #include<sys/socket.h> #include<arpa/inet.h> #include<netinet/in.h> #include<unistd.h> char dns_servers[1][16];//存放DNS服務器的IP int dns_server_count = 0; /* **DNS報文中查詢區域的查詢類型 */ #define A 1 //查詢類型,表示由域名獲得IPv4地址 void ngethostbyname(unsigned char*, int); void ChangetoDnsNameFormat(unsigned char*, unsigned char*); /* **DNS報文首部 **這里使用了位域 */ struct DNS_HEADER { unsigned short id; //會話標識 unsigned char rd :1; // 表示期望遞歸 unsigned char tc :1; // 表示可截斷的 unsigned char aa :1; // 表示授權回答 unsigned char opcode :4; unsigned char qr :1; // 查詢/響應標志,0為查詢,1為響應 unsigned char rcode :4; //應答碼 unsigned char cd :1; unsigned char ad :1; unsigned char z :1; //保留值 unsigned char ra :1; // 表示可用遞歸 unsigned short q_count; // 表示查詢問題區域節的數量 unsigned short ans_count; // 表示回答區域的數量 unsigned short auth_count; // 表示授權區域的數量 unsigned short add_count; // 表示附加區域的數量 }; /* **DNS報文中查詢問題區域 */ struct QUESTION { unsigned short qtype;//查詢類型 unsigned short qclass;//查詢類 }; typedef struct { unsigned char *name; struct QUESTION *ques; } QUERY; /* **DNS報文中回答區域的常量字段 */ //編譯制導命令 #pragma pack(push, 1)//保存對齊狀態,設定為1字節對齊 struct R_DATA { unsigned short type; //表示資源記錄的類型 unsigned short _class; //類 unsigned int ttl; //表示資源記錄可以緩存的時間 unsigned short data_len; //數據長度 }; #pragma pack(pop) //恢復對齊狀態 /* **DNS報文中回答區域的資源數據字段 */ struct RES_RECORD { unsigned char *name;//資源記錄包含的域名 struct R_DATA *resource;//資源數據 unsigned char *rdata; }; int main(int argc, char *argv[]) { unsigned char hostname[100]; unsigned char dns_servername[100]; printf("請輸入DNS服務器的IP:"); scanf("%s", dns_servername); strcpy(dns_servers[0], dns_servername); printf("請輸入要查詢IP的主機名:"); scanf("%s", hostname); //由域名獲得IPv4地址,A是查詢類型 ngethostbyname(hostname, A); return 0; } /* **實現DNS查詢功能 */ void ngethostbyname(unsigned char *host, int query_type) { unsigned char buf[65536], *qname, *reader; int i, j, stop, s; struct sockaddr_in a;//地址 struct RES_RECORD answers[20], auth[20], addit[20];//回答區域、授權區域、附加區域中的資源數據字段 struct sockaddr_in dest;//地址 struct DNS_HEADER *dns = NULL; struct QUESTION *qinfo = NULL; printf("\n所需解析域名:%s", host); s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); //建立分配UDP套結字 dest.sin_family = AF_INET;//IPv4 dest.sin_port = htons(53);//53號端口 dest.sin_addr.s_addr = inet_addr(dns_servers[0]);//DNS服務器IP dns = (struct DNS_HEADER *) &buf; /*設置DNS報文首部*/ dns->id = (unsigned short) htons(getpid());//id設為進程標識符 dns->qr = 0; //查詢 dns->opcode = 0; //標准查詢 dns->aa = 0; //不授權回答 dns->tc = 0; //不可截斷 dns->rd = 1; //期望遞歸 dns->ra = 0; //不可用遞歸 dns->z = 0; //必須為0 dns->ad = 0; dns->cd = 0; dns->rcode = 0;//沒有差錯 dns->q_count = htons(1); //1個問題 dns->ans_count = 0; dns->auth_count = 0; dns->add_count = 0; //qname指向查詢問題區域的查詢名字段 qname = (unsigned char*) &buf[sizeof(struct DNS_HEADER)]; ChangetoDnsNameFormat(qname, host);//修改域名格式 qinfo = (struct QUESTION*) &buf[sizeof(struct DNS_HEADER) + (strlen((const char*) qname) + 1)]; //qinfo指向問題查詢區域的查詢類型字段 qinfo->qtype = htons(query_type); //查詢類型為A qinfo->qclass = htons(1); //查詢類為1 //向DNS服務器發送DNS請求報文 printf("\n\n發送報文中..."); if (sendto(s, (char*) buf,sizeof(struct DNS_HEADER) + (strlen((const char*) qname) + 1)+ sizeof(struct QUESTION), 0, (struct sockaddr*) &dest,sizeof(dest)) < 0) { perror("發送失敗!"); } printf("發送成功!"); //從DNS服務器接受DNS響應報文 i = sizeof dest; printf("\n接收報文中..."); if (recvfrom(s, (char*) buf, 65536, 0, (struct sockaddr*) &dest,(socklen_t*) &i) < 0) { perror("接收失敗!"); } printf("接收成功!"); dns = (struct DNS_HEADER*) buf; //將reader指向接收報文的回答區域 reader = &buf[sizeof(struct DNS_HEADER) + (strlen((const char*) qname) + 1) + sizeof(struct QUESTION)]; printf("\n\n響應報文包含: "); printf("\n %d個問題", ntohs(dns->q_count)); printf("\n %d個回答", ntohs(dns->ans_count)); printf("\n %d個授權服務", ntohs(dns->auth_count)); printf("\n %d個附加記錄\n\n", ntohs(dns->add_count)); /* **解析接收報文 */ reader = reader + sizeof(short);//short類型長度為32為,相當於域名字段長度,這時reader指向回答區域的查詢類型字段 answers[i].resource = (struct R_DATA*) (reader); reader = reader + sizeof(struct R_DATA);//指向回答問題區域的資源數據字段 if (ntohs(answers[i].resource->type) == A) //判斷資源類型是否為IPv4地址 { 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); } //顯示查詢結果 if (ntohs(answers[i].resource->type) == A) //判斷查詢類型IPv4地址 { long *p; p = (long*) answers[i].rdata; a.sin_addr.s_addr = *p; printf("IPv4地址:%s\n", inet_ntoa(a.sin_addr)); } return; } /* **從www.baidu.com轉換到3www5baidu3com */ void ChangetoDnsNameFormat(unsigned char* dns, unsigned char* host) { int lock = 0, i; strcat((char*) host, "."); for (i = 0; i < strlen((char*) host); i++) { if (host[i] == '.') { *dns++ = i - lock; for (; lock < i; lock++) { *dns++ = host[lock]; } lock++; } } *dns++ = '\0'; }
程序中一些代碼和函數的說明:
程序中使用編譯制導命令#pragma pack(n)來設定變量以n字節對齊方式。
其中#pragma pack(push, 1) 保存對齊狀態,設定為1字節對齊
#pragma pack(pop)恢復對齊狀態
htons是將整型變量從主機字節順序轉變成網絡字節順序。
ntohs()由網絡字節順序轉換為主機字節順序。
5效果: