cares 异步 DNS 简介

2015-04-18 linux network

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 中的行为,其基本流程如下:

  1. 根据 RFC-7686 规定,对于 .onion 会直接忽略;
  2. 判断是否为单个域名 (也就是最后一个字符是否为句点),如果是则直接调用 ares_query() 查询;
  3. 模拟 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