Skip to content

Latest commit

 

History

History
214 lines (184 loc) · 7.35 KB

tutorial-17-dns_cli.md

File metadata and controls

214 lines (184 loc) · 7.35 KB

使用workflow请求DNS

作为一款优秀的异步编程框架,workflow帮助用户处理了大量的细节,其中就包括域名解析,因此在大部分情况下,用户无需关心如何请求DNS服务。正如workflow中的其他模块一样,DNS解析模块设计的同样完备而优雅,若恰好需要实现一些域名解析任务,workflow中的WFDnsClient和WFDnsTask无疑是一个绝佳的选择。

about-dns中介绍了如何配置DNS相关参数,而本篇文档的重点在于介绍如何创建DNS任务以及获取解析结果。

tutorial-17-dns_cli.cc

使用WFDnsClient创建任务

WFDnsClient是经过封装的高级接口,其行为类似于系统提供的resolv.conf配置文件,帮助用户代理了重试、search列表拼接、server轮换等功能,使用起来非常简单。WFDnsClient的初始化方式有以下几种情况,当函数返回0时表示初始化成功

  • 使用一个DNS IPv4地址初始化,下述两种写法等价
client.init("8.8.8.8");
// or
client.init("dns://8.8.8.8/");
  • 使用一个DNS IPv6地址初始化
client.init("[2402:4e00::]:53");
  • 使用DNS over TLS(DoT)地址初始化,默认端口号为853
client.init("dnss://120.53.53.53/");
  • 使用多个由逗号分隔的DNS地址初始化
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
client.init("8.8.8.8", "sogou.com,tencent.com", 1, 2, true);

使用WFDnsClient创建的任务默认为DNS_TYPE_ADNS_CLASS_IN类型的解析请求,且已经设置了递归解析的选项,即task->get_req()->set_rd(1)。了解了WFDnsClient的初始化的方式,仅需八行即可发起一个DNS解析任务

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_ADNS_CLASS_IN类型的解析请求,创建后可以通过set_question_typeset_question_class修改,例如

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函数设置域名等参数,例如

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释放资源

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一般用于获取IPv4IPv6地址,而使用DnsResultCursor可以完整地遍历DNS结果。DNS解析结果分为answer、authority、additional三个区域,一般情况下主要内容位于answer区域,此处分别判断每个区域是否有内容,并调用show_result以逐一展示结果

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: 该域名的权威名称
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");
}