c-ares 是一个 C89 实现的异步请求 DNS 的实现,一些常见的软件 (如 curl、NodeJS、WireShark 等等) 都使用了这一软件库。
这里简单介绍其使用方法。
简介
在 CentOS 中,可以直接通过 yum install c-ares-devel
安装开发库,也可以将源码以如下方式编译安装。
$ ./buildconf
$ ./configure
此时将在 .libs
目录下生成相应的动态库和静态库。
通用函数
一些常用的函数。
ares_library_init()
用于初始化c-ares库,实际只有在Windows平台上会执行初始化操作;
ares_library_cleanup()
与上述的init()对应,用于清理c-ares的资源;
Channel
其中 c-ares 会将 ares_channel
作为整个异步请求的上下文 (context),所以在发起一个请求时,应该先初始化 ares_channel
。
在使用时,需要通过 ares_init()
或者 ares_init_options()
函数进行初始化,其中前者使用的是默认参数,后者可以根据需要进行自定义,可以通过如下方式进行修改:
int ares_init_options(ares_channel *channelptr, struct ares_options *options, int optmask);
struct ares_options *options
定义了那些可以修改的参数,而 int optmask
用于指定使用前面结构体中的那些字段,可以在单次调用中通过位与设置多个参数。
在 CentOS 中定义在 /usr/include/ares.h
头文件中,摘抄如下。
#define ARES_OPT_FLAGS (1 << 0)
#define ARES_OPT_TIMEOUT (1 << 1)
#define ARES_OPT_TRIES (1 << 2)
#define ARES_OPT_NDOTS (1 << 3)
#define ARES_OPT_UDP_PORT (1 << 4)
#define ARES_OPT_TCP_PORT (1 << 5)
#define ARES_OPT_SERVERS (1 << 6)
#define ARES_OPT_DOMAINS (1 << 7)
#define ARES_OPT_LOOKUPS (1 << 8)
#define ARES_OPT_SOCK_STATE_CB (1 << 9)
#define ARES_OPT_SORTLIST (1 << 10)
#define ARES_OPT_SOCK_SNDBUF (1 << 11)
#define ARES_OPT_SOCK_RCVBUF (1 << 12)
#define ARES_OPT_TIMEOUTMS (1 << 13)
#define ARES_OPT_ROTATE (1 << 14)
#define ARES_OPT_EDNSPSZ (1 << 15)
struct ares_options {
int flags;
int timeout; // 设置第一次查询的超时时间,默认是5秒
int tries; // 如果失败重试次数
int ndots;
unsigned short udp_port;
unsigned short tcp_port;
int socket_send_buffer_size;
int socket_receive_buffer_size;
struct in_addr *servers; // 设置DNS服务器的地址,可通过ares_init_options()或ares_set_servers()配置
int nservers; // 服务器的地址数
char **domains; // 如果提交的主机名小于ndots时,会尝试添加domain后进行搜索
int ndomains;
char *lookups; // 用字符串表示查询的方式,包括了
ares_sock_state_cb sock_state_cb; // socket状态变化时的回调函数
void *sock_state_cb_data; // 调用删除回调时的入参私有变量
struct apattern *sortlist;
int nsort;
int ednspsz;
};
Process
c-ares 将一些常用的接口进行了封装,但是本身并不会通过数据进行驱动,需要由用户在不同的阶段进行调用,从而可以适配不同用户的不同需求。
int ares_fds(ares_channel channel, fd_set *read_fds, fd_set *write_fds);
int ares_getsock(ares_channel channel, ares_socket_t *socks, int numsocks);
struct timeval *ares_timeout(ares_channel channel, struct timeval *maxtv, struct timeval *tv);
void ares_process(ares_channel channel, fd_set *read_fds, fd_set *write_fds);
void ares_process_fd(ares_channel channel, ares_socket_t read_fd, ares_socket_t write_fd);
如果失败,默认第一次会在 5s 后重试,总共重试 4 次,可以通过 ares_init_options()
进行设置。
源码解析
源码其实比较简单,无非就是如何发送请求报文,然后接收报文,并根据当前的处理状态进行相应的处理。
查询
ares_query()
和 ares_search()
都是用来通过 DNS 查找对应的信息,注意,不会查看 /etc/hosts
信息,但是两者的执行逻辑略有区别。
ares_query()
会直接执行一次 DNS 查询,也就是发送请求,然后接收数据进行处理;而 ares_search()
会模拟 resolv.conf
中的行为,其基本流程如下:
- 根据 RFC-7686 规定,对于
.onion
会直接忽略; - 判断是否为单个域名 (也就是最后一个字符是否为句点),如果是则直接调用
ares_query()
查询; - 模拟 ndots 和 search 的行为,当查询域名句点数小于 ndots 时,会遍历 search 中的选项。
其函数的声明如下。
void ares_query(ares_channel channel, const char *name, int dnsclass, int type,
ares_callback callback, void *arg);
入参:
name 需要查询的域名;
参考
官方网站 c-ares.haxx.se 以及相关的文档 c-ares.haxx.se/docs.html 。