ldns是一个提供DNS解析的开源库,可以实现各种DNS服务器或者客户端的功能,最近项目中有用到,所以就记录一下,需求是这样的:nginx的upstream server中可以配置域名,nginx的upstream模块会解析出域名对应的IP,然后就跟配置IP的server一样了,但这样配置是死的,当域名的A记录改变时(在用DNS来做负载均衡时)就必须重载nginx配置文件才能生效,这样就得重启nginx的work进程,重启过程中可能丢掉一些连接,比较低效,所以我们就用了ldns来做域名解析,当发现A记录改变的时候,每个worker进程重新生成upstream server就可以了,不用重启进程,也不会丢连接,所以效果还不错。
DNS记录中每条记录都有TTL(Time To Live),也就是过期时间,我们就可以根据这个TTL来设置定时器,当定时器过期时就做一次域名解析,解析之后就得到域名对应的CNAME和A记录,当A记录发生变化时再更新upstream server就可以了,这样就可以达到我们的目的了。
先来看看最简单的dig吧:
root@jusse ~# dig www.baidu.com
; <<>> DiG 9.8.1-P1 <<>> www.baidu.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57888
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 5, ADDITIONAL: 0
;; QUESTION SECTION:
;www.baidu.com. IN A
;; ANSWER SECTION:
www.baidu.com. 648 IN CNAME www.a.shifen.com.
www.a.shifen.com. 37 IN A 115.239.210.27
www.a.shifen.com. 37 IN A 115.239.211.112
;; AUTHORITY SECTION:
a.shifen.com. 934 IN NS ns3.a.shifen.com.
a.shifen.com. 934 IN NS ns1.a.shifen.com.
a.shifen.com. 934 IN NS ns5.a.shifen.com.
a.shifen.com. 934 IN NS ns2.a.shifen.com.
a.shifen.com. 934 IN NS ns4.a.shifen.com.
;; Query time: 3 msec
;; SERVER: 10.202.72.116#53(10.202.72.116)
;; WHEN: Wed Jan 7 21:37:35 2015
;; MSG SIZE rcvd: 180
其实主要看ANSWER SECTION这一段,底下是五个字段,第一个就是域名,第二个是TTL,第三个是协议类型(IN、CH、HS……),第四个是记录类型(A、AAAA、CNAME……),第五个是记录类型对应的结果。
我们用ldns来实现这个最简单的dig:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <ldns/ldns.h>
static int
usage(FILE *fp, char *prog) {
fprintf(fp, "%s server nameserver\n", prog);
fprintf(fp, " print out some information about server\n");
return 0;
}
int
main(int argc, char *argv[])
{
ldns_resolver *res;
ldns_rdf *name;
ldns_rdf *nameserver;
ldns_rdf *rr_rdf;
ldns_pkt *p;
ldns_rr_list *info;
ldns_rr *rr;
ldns_status s;
size_t i;
unsigned int addr_ip;
int cname_ttl = 0;
int a_ttl = 0;
char str[16];
if (argc < 2) {
usage(stdout, argv[0]);
exit(EXIT_FAILURE);
} else {
/* create a rdf from the command line arg */
name = ldns_dname_new_frm_str(argv[1]);
if (!name) {
usage(stdout, argv[0]);
exit(EXIT_FAILURE);
}
}
if (argc == 2) {
/* create a new resolver from /etc/resolv.conf */
s = ldns_resolver_new_frm_file(&res, NULL);
if (s != LDNS_STATUS_OK) {
ldns_rdf_deep_free(name);
exit(EXIT_FAILURE);
}
} else {
res = ldns_resolver_new();
nameserver = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_A, argv[2]);//这里可以指定DNS服务器
if (!nameserver) {
printf("nameserver is NULL\n");
exit(EXIT_FAILURE);
}
(void)ldns_resolver_push_nameserver(res, nameserver);
}
ldns_resolver_set_retry(res, 1); /* don't want to wait too long */
p = ldns_resolver_query(res, name, LDNS_RR_TYPE_A, LDNS_RR_CLASS_IN, LDNS_RD);//做DNS查询
if (p) {
//ldns_pkt_print(stdout, p);
info = ldns_pkt_rr_list_by_type(p, LDNS_RR_TYPE_CNAME, LDNS_SECTION_ANSWER);//获取CNAME
if (info) {
ldns_rr_list_print(stdout, info);//打印CNAME记录
cname_ttl = ldns_rr_ttl(ldns_rr_list_rr(info, 0));//获取TTL方法
//printf("cname_ttl: %d\n", cname_ttl);
ldns_rr_list_deep_free(info);
} else {
printf(" *** version retrieval failed\n");
}
info = ldns_pkt_rr_list_by_type(p, LDNS_RR_TYPE_A, LDNS_SECTION_ANSWER);//获取A记录
if (info) {
for(i = 0; i < ldns_rr_list_rr_count(info); i++) {
ldns_rr_print(stdout, ldns_rr_list_rr(info, i));//A记录可能有多个,所以这里循环获取
rr = ldns_rr_list_rr(info, i);
rr_rdf = ldns_rr_rdf(rr, 0);
a_ttl = ldns_rr_ttl(rr);
addr_ip = *(unsigned int *)ldns_rdf_data(rr_rdf);//获取DNS原始报文中的A记录,也就是IP
if (inet_ntop(AF_INET, ldns_rdf_data(rr_rdf), str, sizeof(str))) {
//printf("ip: %s\t", str);
}
//printf("addr_ip: %u, ttl: %d\n", addr_ip, a_ttl);
}
//ldns_rr_list_print(stdout, info);
ldns_rr_list_deep_free(info);
} else {
printf(" *** version retrieval failed\n");
}
ldns_pkt_free(p);
} else {
printf(" *** query failed\n");
}
//ldns_rdf_deep_free(name);//这行去掉,否则跟下一行代码导致重复释放内存而出现coredump
ldns_resolver_deep_free(res);
exit(EXIT_SUCCESS);
}
编译:
gcc -Wall -g -o my_dig ./my_dig.c -lldns
运行:
root@jusse ~/develop# ./my_dig www.baidu.com
www.baidu.com. 206 IN CNAME www.a.shifen.com.
www.a.shifen.com. 266 IN A 115.239.210.27
www.a.shifen.com. 266 IN A 115.239.211.112
root@jusse ~/develop# ./my_dig www.baidu.com 8.8.8.8
www.baidu.com. 219 IN CNAME www.a.shifen.com.
www.a.shifen.com. 299 IN A 115.239.210.27
www.a.shifen.com. 299 IN A 115.239.211.112
ldns第一次接触,入门不光要从它的文档入手,而且更应该从它的例子和源码入手,上面这个例子就是在它的例子ldns-chaos上改写的,源码目录中examples还有很多例子,源码也不大,也比较清晰,有兴趣可以看看。
参考:
http://www.nlnetlabs.nl/projects/ldns/
(全文完)