- 論壇徽章:
- 0
|
Linux下的地址解析函數(shù)應(yīng)用實(shí)例
作者: 默難 ( monnand@gmail.com )
0 引言
域名系統(tǒng)(DNS)是一種用于TCP/IP應(yīng)用程序的分布式數(shù)據(jù)庫(kù), 它提供主機(jī)名字和IP地址之間的轉(zhuǎn)換及有關(guān)電子郵件的選路信息.[1] 目前, 它已經(jīng)在全球范圍內(nèi)被廣泛應(yīng)用. 從應(yīng)用的角度上看, 對(duì)DNS的訪問(wèn)是通過(guò)一個(gè)地址解析器(resolver)來(lái)完成的. 本文通過(guò)講解一些常用的地址解析函數(shù), 并利用精簡(jiǎn)后的部分qmail代碼, 讓不熟悉DNS相關(guān)函數(shù)的程序員了解并掌握常用的地址解析函數(shù).
1 概述
DNS查詢中, 最常用的兩類分別是A類查詢(A query)和指針查詢(PTR query). 前者是已知主機(jī)名, 詢問(wèn)IP; 后者是已知IP, 詢問(wèn)主機(jī)名. 對(duì)于這些查詢, 在Unix主機(jī)中可以直接調(diào)用基本DNS函數(shù): gethostbyname(3)和gethostbyaddr(3)來(lái)實(shí)現(xiàn). 但是對(duì)于其他類型的查詢(例如MX查詢), 則沒(méi)有專門的函數(shù)來(lái)負(fù)責(zé)處理. 此時(shí), 程序員不得不依賴地址解析函數(shù)來(lái)親自處理這些問(wèn)題. 這需要對(duì)DNS報(bào)文格式有基本的了解, 這些將在下面幾節(jié)進(jìn)行說(shuō)明. 關(guān)于gethostbyname(3)和gethostbyaddr(3)兩個(gè)函數(shù), 讀者可以查閱自己系統(tǒng)上的man手冊(cè).
2 DNS報(bào)文格式
在對(duì)地址解析函數(shù)講解之前, 有必要先了解一下DNS報(bào)文格式. 之后的幾節(jié)會(huì)頻繁地涉及到本節(jié)所講的內(nèi)容. 如果想對(duì)DNS相關(guān)協(xié)議有更深的了解, 可以閱讀參考文獻(xiàn)[1] [2] [3].
DNS定義了一個(gè)用于查詢和響應(yīng)的報(bào)文格式, 圖1 顯示了這個(gè)報(bào)文的總體格式.
![]()
[圖1]
每個(gè)DNS查詢(或響應(yīng))報(bào)文都包含有一個(gè)12字節(jié)長(zhǎng)的首部和四個(gè)變長(zhǎng)的字段組成.
對(duì)于本文來(lái)說(shuō), 首部中主要關(guān)心的是問(wèn)題數(shù)和資源記錄數(shù)兩個(gè)字段. 這兩個(gè)字段分別用于說(shuō)明各自對(duì)應(yīng)的變長(zhǎng)字段中的條目數(shù). 問(wèn)題數(shù)說(shuō)明查詢問(wèn)題字段中的條目數(shù); 資源記錄數(shù)則說(shuō)明回答字段中的條目數(shù). 對(duì)于一個(gè)DNS查詢報(bào)文, 問(wèn)題數(shù)通常是1. 對(duì)于應(yīng)答報(bào)文, 回答數(shù)至少是1.
首部以下是四個(gè)變長(zhǎng)字段, 本文所關(guān)心的是查詢問(wèn)題字段和回答字段.
查詢問(wèn)題字段可以包含多個(gè)查詢問(wèn)題, 每個(gè)問(wèn)題的格式如圖2 所示.
![]()
[圖2]
其中, 查詢名一項(xiàng)存儲(chǔ)著要查找的名字. 它長(zhǎng)度可變并以一種特殊的格式存儲(chǔ). 程序可以通過(guò)其中存儲(chǔ)的內(nèi)容確定其長(zhǎng)度. 具體獲得其中存儲(chǔ)內(nèi)容的方法, 將在下一節(jié)中進(jìn)行詳細(xì)講解. 每一個(gè)問(wèn)題有一個(gè)查詢類型, 每個(gè)響應(yīng)(下文中將會(huì)提到)也同樣有一個(gè)類型. 常用的類型有: A類型---表示期望獲得查詢名的IP地址; PTR查詢---表示期望獲得一個(gè)IP地址對(duì)應(yīng)的域名; MX查詢---郵件交換查詢(關(guān)于MX查詢的具體內(nèi)容, 下文會(huì)提到). 查詢類指定了所使用的協(xié)議簇, 通常是1, 表示Internet地址.
回答字段可以包含多個(gè)條目. 每個(gè)回答字段是以一種叫做資源記錄(Resource Record, RR)的格式存儲(chǔ)的. ( 授權(quán)字段和額外信息字段也同樣以資源記錄的格式存儲(chǔ)信息). 資源記錄的格式如圖3 所示.
![]()
[圖3]
域名是記錄中資源數(shù)據(jù)對(duì)應(yīng)的名字. 它的格式和前面介紹的查詢名字段格式相同. 類型和類字段和前面介紹的查詢類型, 查詢類字段的功能一樣. 類字段的取值通常是1, 表示Internet地址. 生存時(shí)間字段是客戶程序保留該資源記錄的秒數(shù). 資源數(shù)據(jù)長(zhǎng)度說(shuō)明資源數(shù)據(jù)包含的字節(jié)數(shù). 資源數(shù)據(jù)則根據(jù)類型字段的值有不同的格式. 對(duì)于A類型, 資源數(shù)據(jù)是IP地址. 對(duì)于MX查詢, 資源數(shù)據(jù)是優(yōu)先值和域名, 域名的格式與查詢名字段格式相同(MX記錄的具體內(nèi)容下文會(huì)有介紹).
至此, DNS中用到的報(bào)文格式已經(jīng)基本介紹完. 下一節(jié)中將會(huì)介紹一些常用的地址解析函數(shù). 閱讀下文時(shí), 最好隨時(shí)翻閱本節(jié)所講的內(nèi)容以便于理解.
3 地址解析函數(shù)
除了經(jīng)常用到的gethostbyname(3)和gethostbyaddr(3)函數(shù)以外, Linux(以及其它UNIX/UNIX-like系統(tǒng))還提供了一套用于在底層處理DNS相關(guān)問(wèn)題的函數(shù)(這里所說(shuō)的底層僅是相對(duì)gethostbyname和gethostbyaddr兩個(gè)函數(shù)而言). 這套函數(shù)被稱為地址解析函數(shù)(resolver functions). 用戶可以通過(guò)鍵入man resolver來(lái)了解其中的具體信息. 這里將對(duì)其中常用到的函數(shù)做一個(gè)解釋. 常用的地址解析函數(shù)原型如下:
- #include <netinet/in.h>
- #include <arpa/nameser.h>
- #include <resolv.h>
- extern struct state _res;
- int res_init(void);
- int res_query(const char *dname, int class, int type,
- unsigned char *answer, int anslen);
- int res_search(const char *dname, int class, int type,
- unsigned char *answer, int anslen);
- int dn_expand(unsigned char *msg, unsigned char *eomorig,
- unsigned char *comp_dn, unsigned char *exp_dn,
- int length);
復(fù)制代碼
_res: 這個(gè)結(jié)構(gòu)體用于保存相關(guān)的狀態(tài)信息. 它的定義在<resolv.h>中.
res_init: 讀取配置文件并修改環(huán)境變量:LOCALDOMAIN. 在調(diào)用其他地址解析函數(shù)前通常要先調(diào)用res_init. 如果執(zhí)行成功, 函數(shù)返回0; 否則返回-1.
res_query: 用來(lái)發(fā)出一個(gè)指定類(由參數(shù)class指定)和類型(由參數(shù)type指定)的DNS詢問(wèn). dname是要查詢的主機(jī)名. 返回信息被存儲(chǔ)在answser指向的內(nèi)存區(qū)域中. 信息的長(zhǎng)度不能大于anslen個(gè)字節(jié). 這個(gè)函數(shù)會(huì)創(chuàng)建一個(gè)DNS查詢報(bào)文并把它發(fā)送到指定的DNS服務(wù)器.
res_search: 和res_query的行為類似, 與res_query不同的是, 當(dāng)域名中不包含點(diǎn)時(shí), 會(huì)在域名后面加上默認(rèn)域名; 同時(shí), 支持遞歸查詢(即當(dāng)一個(gè)服務(wù)器沒(méi)有存儲(chǔ)詢問(wèn)的信息時(shí), 會(huì)繼續(xù)向其他服務(wù)器詢問(wèn)). 一般情況下盡量使用res_search. 因?yàn)樗某晒茁蕰?huì)比較大.
res_query和res_search函數(shù)返回值是響應(yīng)報(bào)文的長(zhǎng)度; 如果發(fā)生錯(cuò)誤則返回-1.
dn_expand: 上一節(jié)中已經(jīng)說(shuō)到, DNS報(bào)文中主機(jī)名是以一種特殊格式存儲(chǔ)的. dn_expand函數(shù)則是將這種特殊格式存儲(chǔ)的字符串還原成一般格式. msg參數(shù)值是整個(gè)DNS報(bào)文的首地址; eomorig參數(shù)指向DNS報(bào)文的最后一個(gè)字節(jié)后的一字節(jié), 用于指定報(bào)文的結(jié)束位置; comp_dn參數(shù)指向報(bào)文中需要被還原的主機(jī)名的首地址; 還原后的主機(jī)名被存儲(chǔ)在exp_dn指向的內(nèi)存區(qū)域中, 長(zhǎng)度不大于length個(gè)字節(jié). 函數(shù)返回主機(jī)名在DNS報(bào)文中的長(zhǎng)度(即被還原前的長(zhǎng)度); 如果發(fā)生錯(cuò)誤則返回-1.
需要注意的是, 如果程序中用到了這些地址解析函數(shù), 那么在編譯的時(shí)候需要加上-lresolv選項(xiàng)才能正常編譯.
利用這些地址解析函數(shù), 不僅可以完成A類查詢或PTR查詢, 還可以進(jìn)行其他類型的詢問(wèn). 下一節(jié)將給出利用地址解析函數(shù)進(jìn)行MX查詢的實(shí)例.
4 地址解析函數(shù)應(yīng)用實(shí)例---MX查詢
在發(fā)送電子郵件時(shí), 需要用到MX(Mail eXchange)記錄. 一個(gè)電子郵箱的地址是 "用戶名@域名" 的格式. 當(dāng)要給某個(gè)用戶發(fā)送電子郵件時(shí), 首先需要從這個(gè)用戶的電子郵箱地址中得到域名; 然后向DNS服務(wù)器發(fā)出一個(gè)MX查詢, 詢問(wèn)該域名由哪些服務(wù)器負(fù)責(zé)處理.DNS服務(wù)器會(huì)返回處理該域名的服務(wù)器的主機(jī)名. 每個(gè)主機(jī)名對(duì)應(yīng)一個(gè)16bit的整數(shù)值, 該值稱為優(yōu)先值(preference value), 如果一個(gè)域存在多條MX記錄, 則首先使用優(yōu)先值較小的主機(jī)名. 之后, 就是利用SMTP協(xié)議與相應(yīng)的主機(jī)進(jìn)行連接并發(fā)送郵件.
如果要發(fā)出一個(gè)MX查詢, 可以利用host命令:
- [monnand@monnand-host ~]$ host -t mx gmail.com
- gmail.com mail is handled by 50 gsmtp183.google.com.
- gmail.com mail is handled by 5 gmail-smtp-in.l.google.com.
- gmail.com mail is handled by 10 alt1.gmail-smtp-in.l.google.com.
- gmail.com mail is handled by 10 alt2.gmail-smtp-in.l.google.com.
- gmail.com mail is handled by 50 gsmtp163.google.com.
復(fù)制代碼
-t 選項(xiàng)用于指明查詢類型, -t mx表示發(fā)起一個(gè)MX查詢. 后面的參數(shù)是要查詢的域名(這里以gmail.com為例).
顯示出的是關(guān)于查詢域名的MX記錄. 這里關(guān)于gmail.com的MX記錄共5條, 每條記錄都有相應(yīng)的優(yōu)先值(顯示在主機(jī)名前面), 例如第一條記錄的優(yōu)先值是50.
下面, 我們就利用前面講到的地址解析函數(shù)來(lái)實(shí)現(xiàn)一個(gè)類似功能的程序. 即指定查詢域名, 打印出關(guān)于這個(gè)域名的MX記錄. 該程序的代碼是從qmail的代碼中精簡(jiǎn)出來(lái)的, 其中去掉了一些錯(cuò)誤檢測(cè), 并修改了與qmail其他部分相關(guān)聯(lián)的代碼, 使整個(gè)程序能夠獨(dú)立出來(lái)(當(dāng)然, 因?yàn)槿サ袅撕芏噱e(cuò)誤檢測(cè), 程序也失去了原有的健壯性和安全性). 但是整體的思路基本沒(méi)有大的改動(dòng). 有興趣的讀者可以自己閱讀qmail的源代碼. 相信會(huì)有更多的收獲.
整個(gè)程序被寫到了兩個(gè)文件中, 分別名為dns.h和dns.c.
以下是dns.h中的內(nèi)容:
- 1 #ifndef DNS_H
- 2 #define DNS_H
- 3
- 4 #define DNS_MSG_END -2
- 5
- 6 #define dns_mx_query(str) dns_resolve((str),T_MX)
- 7 #define dns_mx_expand() dns_findmx(T_MX)
- 8
- 9 #define foreach_mxrr(p,dn) while(dns_mx_expand()!=DNS_MSG_END \
- 10 &&(!dns_get_mxrr(&p,dn,MAXDNAME)))
- 11
- 12 void dns_init(void);
- 13 int dns_get_mxrr(unsigned short *,unsigned char *,unsigned int);
- 14 int dns_resolve(char *,int);
- 15 int dns_findmx(int);
- 16
- 17 #endif /* #ifndef MONNAND_DNS_H */
復(fù)制代碼
該文件中聲明了4個(gè)函數(shù). 為了便于操作, 定義了三個(gè)宏. 關(guān)于其中具體的用法, 之后會(huì)有介紹. 下面給出dns.c中的源代碼:
- 1 #include <stdio.h>
- 2 #include <stdlib.h>
- 3 #include <string.h>
- 4 #include <netdb.h>
- 5 #include <sys/types.h>
- 6 #include <netinet/in.h>
- 7 #include <arpa/nameser.h>
- 8 #include <resolv.h>
- 9 #include <errno.h>
- 10
- 11 #include "dns.h"
- 12
- 13 extern int res_query();
- 14 extern int res_search();
- 15 extern int errno;
- 16 extern int h_errno;
- 17
- 18 static unsigned short getshort(unsigned char *c) { unsigned short u; u = c[0]; return (u << 8) + c[1]; }
- 19
- 20 static union { HEADER hdr; unsigned char buf[PACKETSZ]; } response;
- 21 static int responselen;
- 22 static unsigned char *responseend;
- 23 static unsigned char *responsepos;
- 24 static int numanswers;
- 25 static char name[MAXDNAME];
- 26 unsigned short pref;
- 27
- 28 int dns_resolve(char *domain,int type)
- 29 {
- 30 int n;
- 31 int i;
- 32 errno=0;
- 33 if(NULL == domain)
- 34 return -1;
- 35 responselen = res_search(domain,C_IN,type,response.buf,sizeof(response));
- 36 if(responselen <= 0)
- 37 return -1;
- 38 if(responselen >= sizeof(response))
- 39 responselen = sizeof(response);
- 40 responseend = response.buf + responselen;
- 41 responsepos = response.buf + sizeof(HEADER);
- 42 n = ntohs(response.hdr.qdcount);
- 43 while(n-->0)
- 44 {
- 45 i = dn_expand(response.buf,responseend,responsepos,name,MAXDNAME);
- 46 responsepos += i;
- 47 i = responseend - responsepos;
- 48 if(i < QFIXEDSZ) return -1;
- 49 responsepos += QFIXEDSZ;
- 50 }
- 51 numanswers = ntohs(response.hdr.ancount);
- 52 return numanswers;
- 53 }
- 54
- 55 int dns_findmx(int wanttype)
- 56 {
- 57 unsigned short rrtype;
- 58 unsigned short rrdlen;
- 59 int i;
- 60
- 61 if(numanswers <=0) return DNS_MSG_END;
- 62 numanswers--;
- 63 if(responsepos == responseend) return -1;
- 64 i = dn_expand(response.buf,responseend,responsepos,name,MAXDNAME);
- 65 if(i < 0) return -1;
- 66 responsepos += i;
- 67 i = responseend - responsepos;
- 68 if(i < 10) return -1;
- 69 rrtype = getshort(responsepos);
- 70 rrdlen = getshort(responsepos + 8);
- 71 responsepos += 10;
- 72 if(rrtype == wanttype)
- 73 {
- 74 if(rrdlen < 3)
- 75 return -1;
- 76 pref = (responsepos[0] << 8) + responsepos[1];
- 77 memset(name,0,MAXDNAME);
- 78 if(dn_expand(response.buf,responseend,responsepos + 2,name,MAXDNAME) < 0)
- 79 return -1;
- 80 responsepos += rrdlen;
- 81 return strlen(name);
- 82 }
- 83 responsepos += rrdlen;
- 84 return 0;
- 85 }
- 86
- 87 void dns_init()
- 88 {
- 89 res_init();
- 90 memset(name,0,MAXDNAME);
- 91 }
- 92
- 93 int dns_get_mxrr(unsigned short *p,unsigned char *dn,unsigned int len)
- 94 {
- 95 *p = pref;
- 96 strncpy(dn,name,len);
- 97 if(len < (strlen(name)+1))
- 98 return -1;
- 99 return 0;
- 100 }
- 101
- 102 int main(int argc, char *argv[])
- 103 {
- 104 char dname[MAXDNAME];
- 105 int i;
- 106 unsigned short p;
- 107 dns_init();
- 108 if(argc!=2)
- 109 {
- 110 fprintf(stderr,"bad argument\n");
- 111 exit(-1);
- 112 }
- 113 i = dns_mx_query(argv[1]);
- 114 if(i<0)
- 115 {
- 116 fprintf(stderr,"err\n");
- 117 return 0;
- 118 }
- 119 printf("pref\tdomain name\n");
- 120 foreach_mxrr(p,dname)
- 121 {
- 122 printf("%d\t%s\n",p,dname);
- 123 }
- 124 return 0;
- 125 }
復(fù)制代碼
注釋:
18: getshort函數(shù)從指定地址讀取16bit網(wǎng)絡(luò)字節(jié)順序的數(shù)據(jù), 并將其轉(zhuǎn)換成little-endian的順序返回.
20: 定義了一個(gè)聯(lián)合體變量名為response, 用于存儲(chǔ)DNS響應(yīng)報(bào)文. HEADER是用于存儲(chǔ)DNS首部的結(jié)構(gòu)體, 定義在<arpa/nameser_compat.h>中(通常在/usr/include/arpa/nameser_compat.h中. <arpa/nameser_compat.h>這個(gè)頭文件則在<arpa/nameser.h>中被include. 后面用到的很多宏都定義在<arpa/nameser_compat.h>這個(gè)頭文件中). HEADER結(jié)構(gòu)體中本文會(huì)用到的成員是dncount和ancout, 分別表示問(wèn)題數(shù)和資源記錄數(shù)(參見(jiàn)圖1).PACKETSZ也定義在<arpa/nameser_compat.h>中, 這個(gè)宏表示一個(gè)報(bào)文的最大長(zhǎng)度.
21-26: responselen是響應(yīng)報(bào)文的長(zhǎng)度. responseend指向了響應(yīng)報(bào)文最后一個(gè)字節(jié)之后的一字節(jié), 即response.buf+responselen. responsepos指向了即將處理的字段. numanswers是還未處理的回答數(shù). name用來(lái)存儲(chǔ)主機(jī)名, 長(zhǎng)度是MAXDNAME個(gè)字節(jié). MAXDNAME定義在<arpa/nameser.h>中, 表示域名的最大長(zhǎng)度. pref用來(lái)存儲(chǔ)MX記錄的優(yōu)先值.
28: dns_resolve函數(shù)發(fā)起一個(gè)指定查詢名(domain)和查詢類型(type)的DNS查詢. type可取的值在<arpa/nameser_compat.h>中定義成了宏. 這些宏都是以T_開(kāi)頭. 例如, A類查詢的值是T_A. 對(duì)于MX查詢, 則可以調(diào)用dns_resolve(domain,T_MX)(這也就是dns.h中, dns_mx_query這個(gè)宏所做的). 如果發(fā)生錯(cuò)誤, 函數(shù)返回-1, 否則返回資源記錄數(shù)(參見(jiàn)圖1).
35: 利用res_search函數(shù)發(fā)起一條指定類型的DNS查詢. 其中的參數(shù)C_IN表示Internet地址. C_IN這個(gè)宏也定義在<arpa/nameser_compat.h>中. 返回值賦給responselen, 即響應(yīng)報(bào)文長(zhǎng)度.
36: 檢查是否有錯(cuò)誤發(fā)生, 如果發(fā)生返回-1. 實(shí)際上這種錯(cuò)誤檢測(cè)是不完善的. 關(guān)于詳細(xì)的錯(cuò)誤檢測(cè)方法可以參考qmail源代碼中的dns.c文件.
40-42: 調(diào)整responseend和responsepos的值. 并將報(bào)文中的問(wèn)題數(shù)存儲(chǔ)在臨時(shí)變量n中, 用于之后的處理. 由于響應(yīng)報(bào)文中的數(shù)據(jù)都是網(wǎng)絡(luò)字節(jié)順序, 因此需要調(diào)用ntohs函數(shù)進(jìn)行轉(zhuǎn)換. ntohs函數(shù)的具體內(nèi)容可以參考man手冊(cè).調(diào)整后, responsepos指向第一個(gè)查詢問(wèn)題的首地址.
43-50: 跳過(guò)所有的查詢問(wèn)題, 讓responsepos指向第一個(gè)回答的首地址. 此時(shí), 需要參考第二節(jié)中的內(nèi)容來(lái)幫助理解. 首先利用dn_expand函數(shù)獲得查詢問(wèn)題字段中查詢名的長(zhǎng)度, 并把該值賦給i. 之后, responsepos+=i使得responsepos指向了查詢類型字段的首地址(參見(jiàn)圖2). QFIXEDSZ宏定義在<arpa/nameser_compat.h>中, 其值等于4. 它表示DNS查詢報(bào)文中問(wèn)題部分的定長(zhǎng)字段的字節(jié)數(shù), 即查詢類型和查詢類兩個(gè)字段的總長(zhǎng)度. i = responseend - responsepos令i的值等于responsepos和responseend之間的距離, 由于此時(shí)responsepos指向問(wèn)題部分查詢類型的第一個(gè)字節(jié), 因此, 對(duì)于一個(gè)正常的報(bào)文來(lái)說(shuō), i的值應(yīng)該至少等于4. 因此, 在第48行進(jìn)行了檢測(cè), 若i < QFIXEDSZ, 表明該報(bào)文格式有錯(cuò). 則返回-1. 否則, responsepos+=QFIXEDSZ. 此時(shí), responsepos指向了下一個(gè)查詢問(wèn)題字段(或第一個(gè)回答字段)的首地址. 如此反復(fù), 直至跳過(guò)全部的查詢問(wèn)題字段. 則循環(huán)執(zhí)行完, responsepos指向第一個(gè)回答字段的首地址.
51-52: 將資源記錄數(shù)的值賦給numanswers. 返回numansers.
55: dns_findmx函數(shù)用于在調(diào)用了dns_resolve函數(shù)之后, 分析DNS響應(yīng)報(bào)文中的回答字段. 如果當(dāng)前responsepos指向的回答字段的類型是參數(shù)wanttype指定的類型, 則對(duì)該回答字段進(jìn)行處理. 參數(shù)wanttype可取的值與dns_resolve函數(shù)中的type參數(shù)可取值一樣. 對(duì)于MX記錄, 值為T_MX. dns.h中dns_mx_expand宏的定義就是dns_findmx(T_MX). 該函數(shù)若發(fā)生錯(cuò)誤則返回-1, 若報(bào)文中已經(jīng)沒(méi)有可以處理的字段, 則返回DNS_MSG_END. 否則返回name數(shù)組存儲(chǔ)的字符串長(zhǎng)度(不包括結(jié)尾的'\0').
61-62: 檢查未處理的回答字段數(shù)目, 若達(dá)到或小于零, 則返回DNS_MSG_END. 否則numanswers值減一.
64: 回答字段是以資源記錄格式存儲(chǔ)的. 第一項(xiàng)是域名(參見(jiàn)圖3). 因此用dn_expand函數(shù)將該字段還原為普通字符串格式. 并將返回的該字段長(zhǎng)度賦值給i.
66: 調(diào)整responsepos的值, 使其跳過(guò)域名部分, 指向類型字段(參見(jiàn)圖3).
67-68: 資源記錄中, 定長(zhǎng)字段的總長(zhǎng)度為10字節(jié),即類型, 類, 生存時(shí)間和資源數(shù)據(jù)長(zhǎng)度字段的總長(zhǎng)度(參見(jiàn)圖3). 若responseend-responsepos的值小于10, 則表明該報(bào)文非法.
69-71: 此時(shí)responsepos指向了資源記錄中類型字段的首地址. 利用getshort函數(shù)分別獲得類型和資源數(shù)據(jù)長(zhǎng)度字段的值, 存儲(chǔ)在rrtype和rrdlen變量中. 之后,調(diào)整responsepos的值, 使其指向資源數(shù)據(jù)的首地址(參見(jiàn)圖3). 對(duì)于一個(gè)MX查詢, 資源數(shù)據(jù)中的內(nèi)容是優(yōu)先值和主機(jī)名.
72-82: 如果這條資源記錄的類型是函數(shù)參數(shù)所指定的類型, 則對(duì)其進(jìn)行處理. MX記錄中, 資源數(shù)據(jù)內(nèi)第一項(xiàng)存儲(chǔ)著16bit的優(yōu)先值, 之后存儲(chǔ)著主機(jī)名. 優(yōu)先值長(zhǎng)度為2字節(jié), 主機(jī)名長(zhǎng)度至少1字節(jié), 則資源數(shù)據(jù)長(zhǎng)度至少要3字節(jié). 否則報(bào)文格式為非法. 第74, 75行就是利用這種方法檢測(cè)資源數(shù)據(jù)是否為非法. 第76行從資源數(shù)據(jù)中獲得優(yōu)先值存儲(chǔ)在pref中. 由于報(bào)文中的優(yōu)先值是按照網(wǎng)絡(luò)字節(jié)順序存儲(chǔ)的, 因此需要將其轉(zhuǎn)換成little-endian順序后存到pref中. 接著對(duì)name數(shù)組清零. 之后從資源數(shù)據(jù)中提取主機(jī)名. responsepos+=rrdlen令responsepos指向了下一條資源數(shù)據(jù). 最后利用strlen函數(shù)返回主機(jī)名的字符串長(zhǎng)度.
83: 該資源記錄的類型與參數(shù)指定類型不符, 則跳過(guò)該條記錄, 將responsepos調(diào)整至下一條資源記錄.
87-91: 調(diào)用res_init函數(shù)執(zhí)行相關(guān)初始化操作. 并對(duì)name數(shù)組清零. 此處沒(méi)有對(duì)res_init的返回值進(jìn)行檢測(cè).
93-100: dns_get_mxrr函數(shù)將pref的值(優(yōu)先值)存儲(chǔ)到p指向的地址中, 并將name中存儲(chǔ)的主機(jī)名復(fù)制到dn指向的地址中, 復(fù)制的長(zhǎng)度不超過(guò)len個(gè)字節(jié). 如果len個(gè)字節(jié)不足以存儲(chǔ)整個(gè)主機(jī)名, 則返回-1, 否則返回0.
102: main函數(shù), argv[1]中會(huì)存儲(chǔ)著要查詢的域名.
104-106: dname用于存儲(chǔ)查找到的主機(jī)名. 變量i用于存儲(chǔ)相關(guān)函數(shù)的返回值. p用于存儲(chǔ)優(yōu)先值.
108-112: 用戶提供的參數(shù)有錯(cuò)誤.
113: 利用dns.h中定義的宏, 發(fā)起MX查詢. 該語(yǔ)句相當(dāng)與i = dns_resolve(argv[1],T_MX);
120: 利用dns.h中定義的宏, 對(duì)響應(yīng)報(bào)文中的每條資源記錄進(jìn)行處理. foreach_mxrr宏在dns.h中定義, 該語(yǔ)句相當(dāng)于while(dns_findmx(T_MX)!=DNS_MSG_END&&(!dns_get_mxrr(&p,dname,MAXDNAME)))
121-124: 打印每條資源記錄后函數(shù)返回.
該程序在Magic Linux 2.0正式版上編譯成功( 內(nèi)核版本2.6.15.3; gcc版本3.4.4; i386體系結(jié)構(gòu) ).編譯和運(yùn)行結(jié)果如下:
- [monnand@monnand-host src]$ gcc -lresolv dns.c -o dns
- [monnand@monnand-host src]$ ./dns gmail.com
- pref domain name
- 50 gsmtp163.google.com
- 50 gsmtp183.google.com
- 5 gmail-smtp-in.l.google.com
- 10 alt1.gmail-smtp-in.l.google.com
- 10 alt2.gmail-smtp-in.l.google.com
復(fù)制代碼
5 后記
本文簡(jiǎn)要介紹了DNS報(bào)文格式, 講解了常用的地址解析函數(shù)并給出了這些函數(shù)的應(yīng)用實(shí)例. 第4節(jié)中的代碼可以應(yīng)用在其他程序內(nèi), 但是使用前最好對(duì)其進(jìn)行一定的修改從而增強(qiáng)健壯性和安全性. 修改時(shí)可以參考qmail的源代碼中dns.c和dns.h兩個(gè)文件. 如果想了解更多的地址解析函數(shù), 可以查閱man resolver. 如果想對(duì)DNS相關(guān)的協(xié)議及報(bào)文格式有更深的了解, 可以查閱附錄中的參考文獻(xiàn).
附錄 參考文獻(xiàn)
[1]W. Richard Stevens.TCP/IP詳解 卷1:協(xié)議.北京:機(jī)械工業(yè)出版社.1993.
[2]Mockapetris, P. V. "Domain Names: Concepts and Facilities," RFC 1034. 1987
[3]Mockapetris, P. V. "Domain Names: Implementation and Specification," RFC1035. 1987[/code] |
|