resolver,DNS 解析库
/etc/resolv.conf;resolver 的配置文件,resolver 会读取该文件初始化解析器的行为.
文件格式;resolv.conf 文件是由一组关键词及其值组成,若一行以";"/"#"开头,则表明该行是注释行;否则该行存放着一个关键词及其对应的取值;如下文件包含了"nameserver",以及"options"两个关键词;其中"127.0.1.1"为关键词"nameserver"的取值;"ndots:1"为关键词"options"的取值.
; helloworld,注释行. nameserver 127.0.1.1 options ndots:1
nameserver;参考 man 5 resolv.conf;关键是 resolver 在有多个 DNS 服务器时的使用算法:
// resolver 对含有多个 DNS Server 时使用的算法,假设当前有 A,B 2 个 DNS 服务器 for( i=0;i<最大重试次数;++i) do{ ret = use(A); // 使用 A. if( ret == SUCCESS) // 若 A 成功返回结果,则中止 break; ret = use(B); // 否则使用 B 来解析请求. if( ret == SUCCESS) break; // 如果 B 也失败,则再来一次. }while(0); // 我最初的想法是,当 A 超时时,再对 A 发送请求,直至达到最大重试次数后,再使用 B.
这里只介绍了 man 手册中不清晰的地方,
RES_DEFNAMES,若在 _res.options 中指定该标志,则若 res_search() 中指定的 dname 中不含有一个'.',则会将 resolv.conf 中 domain 关键词的值追加到在 dname 之后,形成一个完全限定域名.
RES_DNSRCH,若在 _res.options 中指定该标志,则 res_search() 会根据 ndots 的值来判断 dname 是否是一个完全限定域名(如 dname 中'.'的个数 < ndots,则认为不是).若 dname 被认为是一个完全限定域名,则直接查询 dname;否则若 dnmae 被认为不是完全限定域名,则会依次在 search 关键词中指定的域表下搜索 dname(若返回"没有域名"的错误,则使用下一个域测试);如下:
// resolv.conf 配置 search xxx.yyy.net yyy.net net options ndots:2 res_search("www.xxx",,,); // 则此时会依次查询
int dn_comp(unsigned char *domain, unsigned char *comp_dn,int length, unsigned char **dnptrs, unsigned char **lastdnptr);
domain;待压缩的域名,
comp_dn,length;[comp_dn,comp_dn+length) 存放 domain 压缩后的结果.
[dnsptr,lastdnptr) 确定了 u_char* 类型的指针数组,其中 dnptrs 指向着数组的第一个元素,lastdnptr-dnptrs 确定了数组的长度.这个数组存放着在当前 DNS 消息中,已经压缩的域名的地址.其中第一个元素(dnptrs[0])指向着 DNS 消息的首部,以 NULLL 来表示有效元素的结束.在调用 dn_comp() 压缩域名之后,会将 comp_dn 也追加到数组 dnptrs 中.
若 dnptrs 为 0,则不对 domain 进行压缩,即直接将其存放 [comp_dn,comp_dn+length) 中;
若 lastdnptr 不为 0,则不更新数组 dnptrs,即不将 comp_dn 追加到当前数组中.
void printDnptrs(u_char **dnptrs,int dnptrsSize){ for(int i=0;i<dnptrsSize;++i) printf("%p ",dnptrs[i]); putchar('\n'); } /// 依据 16 进制(不带 0x 前缀)输出 [src,src+size) 中每一个字节的值 void dump(void *src,size_t size){ unsigned char *s = (unsigned char*)src; for(size_t i = 0;i<size;++i) printf("%02hhx ",s[i]); putchar('\n'); } int main(int argc,char *argv[]){ const int bufSize = 512; u_char buf[bufSize] = {0}; u_char *dnsMsgBuf = buf; u_char * const dnsMsgBufEnd = dnsMsgBuf+bufSize; // [dnsMsgBuf,dnsMsgBufEnd) 存放 dns 消息. // 现在希望将用户在命令行参数中指定的域名都以压缩存储的形式存放在 dns 消息中. // dnptrsSize 取值为 7,去除 dnptrs[0],以及表示有效信息结束的 NULL,剩下 5 个元素用于 // 存放压缩过的域名地址,这表明在压缩第 6 个域名时会根据 dnptrs 中存放的地址得到前 5 个 // 域名压缩信息,从而在压缩第 6 个域名时可以使用这些信息;但在压缩之后由于 dnptrs 没有空间, // 所以第 6 个域名压缩之后的地址不能存放在 dnptrs 中,即在压缩第 7 个域名时使用的仍是前 // 5 个域名压缩后的信息. const int dnptrsSize = 7; u_char *dnptrs[dnptrsSize] = {dnsMsgBuf,0}; // 在调用 dn_comp 之前务忘初始化 u_char ** const dnptrsEnd = dnptrs + dnptrsSize; int ret = 0; u_char *nextDomainPtr = dnsMsgBuf+12; // 第一个域名的存放地址,12 为 DNS 首部长. for(int i = 1; i<argc; ++i){ printf("%d:压缩;域名:%s;",i,argv[i]); ret = dn_comp(argv[i],nextDomainPtr,dnsMsgBufEnd-nextDomainPtr, dnptrs,dnptrsEnd); if(ret < 0){ printf("err:%m\n"); break; } printf("压缩后长度:%d\n",ret); nextDomainPtr += ret; nextDomainPtr += 4; printf("%d:",i); printDnptrs(dnptrs,dnptrsSize); } if(ret < 0) return 1; puts("构造好的 DNS 报文:"); // dump(dnsMsgBuf,dnsMsgBufEnd-dnsMsgBuf); write(2,dnsMsgBuf,dnsMsgBufEnd-dnsMsgBuf); return 0; } // 运行: $ ./Test www.360.com www.qikoo.com www.haosou.com 2>dnsmsg.txt 1:压缩;域名:www.360.com;压缩后长度:13 1:0x7fffa45e8080 0x7fffa45e808c (nil) (nil) (nil) (nil) (nil) 2:压缩;域名:www.qikoo.com;压缩后长度:12 2:0x7fffa45e8080 0x7fffa45e808c 0x7fffa45e809d (nil) (nil) (nil) (nil) 3:压缩;域名:www.haosou.com;压缩后长度:13 3:0x7fffa45e8080 0x7fffa45e808c 0x7fffa45e809d 0x7fffa45e80ad (nil) (nil) (nil) 构造好的 DNS 报文: $ od -A d -t x1z dnsmsg.txt 0000000 00 00 00 00 00 00 00 00 00 00 00 00 03 77 77 77 >.............www< 0000016 03 33 36 30 03 63 6f 6d 00 00 00 00 00 03 77 77 >.360.com......ww< 0000032 77 05 71 69 6b 6f 6f c0 14 00 00 00 00 03 77 77 >w.qikoo.......ww< 0000048 77 06 68 61 6f 73 6f 75 c0 14 00 00 00 00 00 00 >w.haosou........< 0000064 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >................< * 0000512 // 可以看到 与 中的".com"都是使用 的压缩.
int dn_expand(unsigned char *msg, unsigned char *eomorig, unsigned char *comp_dn, char *exp_dn,int length);
msg,eomorig;[msg,eomorig) 中存放着一条完整的 DNS 报文.在 man 手册中,只说明 msg 指向着报文的开始,而没有说明 eomorig 的功能,经过猜测与实现,eomorig 的全称应该为 end of msg origin;
// 现在[dnsMsgBuf,dnsMsgBufEnd)存放着 DNS 报文;而且数组 dnptrs 中存放着域名的地址. // 此时调用 dn_expand() 解压出这些域名. char domainBuf[256]; for(int i=1;dnptrs[i]!=0;++i){ ret = dn_expand(dnsMsgBuf,dnsMsgBufEnd, dnptrs[i],domainBuf,sizeof(domainBuf)); if(ret < 0){ printf("err:%m\n"); break; } printf("%d:%s\n",i,domainBuf); } // 该段代码追加在上面代码"write(2,,)"之后,return 0 之前. // 运行: 1:www.360.com 2:www.qikoo.com 3:www.haosou.com