mirror of
https://github.com/sogou/workflow.git
synced 2026-02-08 01:33:17 +08:00
215 lines
7.3 KiB
Markdown
215 lines
7.3 KiB
Markdown
# 使用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");
|
||
}
|
||
```
|