1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
|
# 使用workflow请求DNS
作为一款优秀的异步编程框架,workflow帮助用户处理了大量的细节,其中就包括域名解析,因此在大部分情况下,用户无需关心如何请求DNS服务。正如workflow中的其他模块一样,DNS解析模块设计的同样完备而优雅,若恰好需要实现一些域名解析任务,workflow中的WFDnsClient和WFDnsTask无疑是一个绝佳的选择。
[about-dns](about-dns.md)中介绍了如何配置DNS相关参数,而本篇文档的重点在于介绍如何创建DNS任务以及获取解析结果。
[tutorial-17-dns_cli.cc](/tutorial/tutorial-17-dns_cli.cc)
## 使用WFDnsClient创建任务
WFDnsClient是经过封装的高级接口,其行为类似于系统提供的`resolv.conf`配置文件,帮助用户代理了重试、search列表拼接、server轮换等功能,使用起来非常简单。WFDnsClient的初始化方式有以下几种情况,当函数返回0时表示初始化成功
- 使用一个DNS IPv4地址初始化,下述两种写法等价
```cpp
client.init("8.8.8.8");
// or
client.init("dns://8.8.8.8/");
```
- 使用一个DNS IPv6地址初始化
```cpp
client.init("[2402:4e00::]:53");
```
- 使用DNS over TLS(DoT)地址初始化,默认端口号为853
```cpp
client.init("dnss://120.53.53.53/");
```
- 使用多个由逗号分隔的DNS地址初始化
```cpp
client.init("dns://8.8.8.8/,119.29.29.29");
```
- 显式指定重试策略的初始化,示例代码等价于下述`resolv.conf`描述的策略
```
nameserver 8.8.8.8
search sogou.com tencent.com
options nodts:1 attempts:2 rotate
```
```cpp
client.init("8.8.8.8", "sogou.com,tencent.com", 1, 2, true);
```
使用WFDnsClient创建的任务默认为`DNS_TYPE_A`、`DNS_CLASS_IN`类型的解析请求,且已经设置了递归解析的选项,即`task->get_req()->set_rd(1)`。了解了`WFDnsClient`的初始化的方式,仅需八行即可发起一个DNS解析任务
```cpp
int main()
{
WFDnsClient client;
client.init("8.8.8.8");
WFDnsTask *task = client.create_dns_task("www.sogou.com", dns_callback);
task->start();
pause();
client.deinit();
return 0;
}
```
## 使用工厂函数创建任务
若不需要WFDnsClient提供的额外功能,或想自行组织重试策略,可使用工厂函数创建任务。
使用工厂函数创建任务时,可以在`url path`中指定要被解析的域名,工厂函数创建的任务默认为`DNS_TYPE_A`、`DNS_CLASS_IN`类型的解析请求,创建后可以通过`set_question_type`和`set_question_class`修改,例如
```cpp
std::string url = "dns://8.8.8.8/www.sogou.com";
WFDnsTask *task = WFTaskFactory::create_dns_task(url, 0, dns_callback);
protocol::DnsRequest *req = task->get_req();
req->set_rd(1);
req->set_question_type(DNS_TYPE_AAAA);
req->set_question_class(DNS_CLASS_IN);
```
若不在创建任务时指定要被解析的域名(此时默认的任务是对根域名`.`进行解析),在创建任务后可以使用`set_question`函数设置域名等参数,例如
```cpp
std::string url = "dns://8.8.8.8/";
WFDnsTask *task = WFTaskFactory::create_dns_task(url, 0, dns_callback);
protocol::DnsRequest *req = task->get_req();
req->set_rd(1);
req->set_question("www.zhihu.com", DNS_TYPE_AAAA, DNS_CLASS_IN);
```
## 借助工具获取结果
一次成功的DNS请求会获得完整的DNS请求结果,有两种简便的接口可以从结果中获取信息
### DnsUtil::getaddrinfo
该函数类似于系统的`getaddrinfo`函数,调用成功时返回零并成功获得一组`struct addrinfo`,调用失败时返回`EAI_*`类型的错误码。对该函数的成功调用最终**都应该**使用`DnsUtil::freeaddrinfo`释放资源
```cpp
void dns_callback(WFDnsTask *task)
{
// ignore handle error states
struct addrinfo *res;
protocol::DnsResponse *resp = task->get_resp();
int ret = protocol::DnsUtil::getaddrinfo(resp, 80, &res);
// ignore check ret == 0
char ip_str[INET6_ADDRSTRLEN + 1] = { 0 };
for (struct addrinfo *p = res; p; p = p->ai_next)
{
void *addr = nullptr;
if (p->ai_family == AF_INET)
addr = &((struct sockaddr_in *)p->ai_addr)->sin_addr;
else if (p->ai_family == AF_INET6)
addr = &((struct sockaddr_in6 *)p->ai_addr)->sin6_addr;
if (addr)
{
inet_ntop(p->ai_family, addr, ip_str, p->ai_addrlen);
printf("ip:%s\n", ip_str);
}
}
protocol::DnsUtil::freeaddrinfo(res);
}
```
### DnsResultCursor
`DnsUtil::getaddrinfo`一般用于获取`IPv4`、`IPv6`地址,而使用DnsResultCursor可以完整地遍历DNS结果。DNS解析结果分为answer、authority、additional三个区域,一般情况下主要内容位于answer区域,此处分别判断每个区域是否有内容,并调用`show_result`以逐一展示结果
```cpp
void dns_callback(WFDnsTask *task)
{
// ignore handle error states
protocol::DnsResponse *resp = task->get_resp();
protocol::DnsResultCursor cursor(resp);
if(resp->get_ancount() > 0)
{
cursor.reset_answer_cursor();
printf(";; ANSWER SECTION:\n");
show_result(cursor);
}
if(resp->get_nscount() > 0)
{
cursor.reset_authority_cursor();
printf(";; AUTHORITY SECTION\n");
show_result(cursor);
}
if(resp->get_arcount() > 0)
{
cursor.reset_additional_cursor();
printf(";; ADDITIONAL SECTION\n");
show_result(cursor);
}
}
```
根据请求类型不同,结果中包含的数据可以多种多样,常见的有
- DNS_TYPE_A: IPv4类型的地址
- DNS_TYPE_AAAA: IPv6类型的地址
- DNS_TYPE_NS: 该域名的权威DNS服务器
- DNS_TYPE_CNAME: 该域名的权威名称
```cpp
void show_result(protocol::DnsResultCursor &cursor)
{
char information[1024];
const char *info;
struct dns_record *record;
struct dns_record_soa *soa;
struct dns_record_srv *srv;
struct dns_record_mx *mx;
while(cursor.next(&record))
{
switch (record->type)
{
case DNS_TYPE_A:
info = inet_ntop(AF_INET, record->rdata, information, 64);
break;
case DNS_TYPE_AAAA:
info = inet_ntop(AF_INET6, record->rdata, information, 64);
break;
case DNS_TYPE_NS:
case DNS_TYPE_CNAME:
case DNS_TYPE_PTR:
info = (const char *)(record->rdata);
break;
case DNS_TYPE_SOA:
soa = (struct dns_record_soa *)(record->rdata);
sprintf(information, "%s %s %u %d %d %d %u",
soa->mname, soa->rname, soa->serial, soa->refresh,
soa->retry, soa->expire, soa->minimum
);
info = information;
break;
case DNS_TYPE_SRV:
srv = (struct dns_record_srv *)(record->rdata);
sprintf(information, "%u %u %u %s",
srv->priority, srv->weight, srv->port, srv->target
);
info = information;
break;
case DNS_TYPE_MX:
mx = (struct dns_record_mx *)(record->rdata);
sprintf(information, "%d %s", mx->preference, mx->exchange);
info = information;
break;
default:
info = "Unknown";
}
printf("%s\t%d\t%s\t%s\t%s\n",
record->name, record->ttl,
dns_class2str(record->rclass),
dns_type2str(record->type),
info
);
}
printf("\n");
}
```
|