Libevent:11使用Libevent的DNS上层和底层功能

         Libevent提供了一些API用来进行DNS域名解析,并且提供了实现简单DNS服务器的能力。

         本章首先描述域名解析的上层功能,然后介绍底层功能及服务器功能。

 

         注意:Libevent的当前DNS客户端实现有一些限制,它不支持TCP查询,DNSSsec或者任意记录类型。将来的Libevent版本中可能会修复这些问题。

 

前言:可移植的阻塞型域名解析

         为了帮助移植那些已经使用阻塞域名解析的应用程序,Libevent提供了一个标准getaddrinfo接口的可移植实现。这对于需要运行在某些特殊平台上的应用程序是很有帮助的,这些特殊平台要么没有提供getaddrinfo接口,要么其getaddrinfo接口不如我们的实现那样符合标准。

         getaddrinfo接口在RFC3493的6.1章进行描述。下面的“兼容性注意事项”部分总结了在哪些方面还缺乏一致的实现。

struct  evutil_addrinfo {
    int  ai_flags;
    int  ai_family;
    int  ai_socktype;
    int  ai_protocol;
    size_t  ai_addrlen;
    char  * ai_canonname;
    struct  sockaddr  *ai_addr;
    struct  evutil_addrinfo  *ai_next;
};
#define  EVUTIL_AI_PASSIVE     /* ... */
#define  EVUTIL_AI_CANONNAME   /* ... */
#define  EVUTIL_AI_NUMERICHOST /* ... */
#define  EVUTIL_AI_NUMERICSERV /* ... */
#define  EVUTIL_AI_V4MAPPED    /* ... */
#define  EVUTIL_AI_ALL         /* ... */
#define  EVUTIL_AI_ADDRCONFIG  /* ... */

 

int  evutil_getaddrinfo(const  char  *nodename, const  char  *servname,
              const struct  evutil_addrinfo  *hints,  struct  evutil_addrinfo  **res);

void  evutil_freeaddrinfo(struct  evutil_addrinfo  *ai);

const  char  *evutil_gai_strerror(int err);

         evutil_getaddrinfo函数尝试根据hints中的规则,解析nodename和servname,并在evutil_addrinfo结构的链表res中返回解析的结果。该函数返回0表示成功,失败时返回一个非0的错误码。

         nodename和servname两者必须提供一个,如果提供了nodename,则它可以是一个文字型的IPv4地址(类似于“127.0.0.1”),或者一个文字型的IPv6地址(比如“::1”),或者是一个域名(比如“www.example.com”)。如果提供了servname,则它既可以是网络服务的符号名称(比如“https”),或者是一个十进制端口号的字符串(比如“443”)。

         如果没有指定servname,则在*res中,端口号将置为0。如果不指定nodename,则*res中的地址要么是默认的本地地址,要么是“any”(如果设置了EVUTIL_AI_PASSIVE的话)

 

         hints中的ai_flags字段提示evutil_getaddrinfo如何进行查找。它可以是0,或者是下列标志的组合:

EVUTIL_AI_PASSIVE:该标志表明,解析的地址将用来被动监听,而不是主动建链。一般情况下两者没有区别,除非nodenameNULL:对于主动建链来说,一个为NULLnodename意味着本地地址(127.0.0.1::1),而对于被动监听来说,一个为NULLnodename意味着ANY0.0.0.0::0.

         EVUTIL_AI_CANONNAME:如果设置了该标志,则会尝试在ai_canonname字段报告主机的规范名称。

         EVUTIL_AI_NUMERICHOST:如果设置了该标志,则仅会解析数字型的IPv4以及IPv6地址;如果nodename是一个需要解析的域名的话,则该函数会返回EVUTIL_EAI_NONAME错误。

         EVUTIL_AI_NUMERICSERV:如果设置了该标志,则仅会解析数字型的服务名,如果servname既不是NULL,也不是十进制整数的话,则返回EVUTIL_EAI_NONAME错误。        

         EVUTIL_AI_V4MAPPED:该标志表明,如果ai_family为AF_INET6,并且没有发现IPv6地址的话,则会将任一IPv4地址返回为一个v4映射(v4-mapped)的IPv6地址。除非操作系统支持,否则当前的evutil_getaddrinfo不支持该标志。

         EVUTIL_AI_ALL:如果将该标志和EVUTIL_AI_V4MAPPED一起设置,则结果集中的IPv4地址都将返回为v4映射的IPv6地址,而不管是否找到了IPV6地址。除非操作系统支持,否则当前的evutil_getaddrinfo不支持该标志。

         EVUTIL_AI_ADDRCONFIG:设置了该标志,则只有在系统具有非本地IPV4地址的时候,结果集中才包含IPv4地址,并且只有在系统具有非本地的IPv6地址的时候,结果集中才包含IPv6地址。

 

         hints中的ai_family字段用来告知evutil_getaddrinfo返回何种地址类型。设置为AF_INET,则仅返回IPv4地址,置为AF_INET6,则仅返回IPv6地址,置为AF_UNSPEC,则返回所有可能的地址。

         hints中的ai_socktype和ai_protocol字段用来告知evutil_getaddrinfo如何使用这些地址。他们类似于传递给socket函数的socktype和protocol参数。

         如果evutil_getaddrinfo执行成功,则返回一个evutil_addrinfo结构的链表res,每个结构中的ai_next指针指向下一个结构。这种链表是在堆中分配的,所以需要使用evutil_freeaddrinfo函数进行释放。

 

         如果该函数执行失败,则返回下列数字错误码:

         EVUTIL_EAI_ADDRFAMILY:请求的地址族对于nodename来说没有意义。

         EVUTIL_EAI_AGAIN:在域名解析过程中发生了可恢复的错误,可以过后再次尝试。

         EVUTIL_EAI_FAIL:域名解析过程中发生了不可恢复的错误,DNS服务器或者解析器可能已经崩溃了。

         EVUTIL_EAI_BADFLAGS:hints中的ai_flags无效。

         EVUTIL_EAI_FAMILY:不支持hints中的ai_family字段。

         EVUTIL_EAI_MEMORY:解析过程中内存不足。

         EVUTIL_EAI_NODATA:请求的主机虽然存在,但是却没有地址信息(或者对于请求的类型,没有相应的地址信息。)

         EVUTIL_EAI_NONAME:请求的主机不存在。

         EVUTIL_EAI_SERVICE:请求的服务不存在。

         EVUTIL_EAI_SOCKTYPE:不支持请求的socket类型,或者它与ai_protocol不匹配。

         EVUTIL_EAI_SYSTEM:域名解析过程中发生了其他系统错误,可以通过查看errno获得更多信息。

         EVUTIL_EAI_CANCEL:DNS请求过程应该在完成之前被取消。evutil_getaddrinfo从不返回该错误,但是在下面介绍的evdns_getaddrinfo中可能会返回该错误。

 

         可以通过evutil_gai_strerror函数,将这些错误码转换为一个可读的字符串信息。

 

         注意:如果操作系统定义了addrinfo结构,则evutil_addrinfo结构仅仅是操作系统内部结构的别名。类似的,如果操作系统定义了任何AI_*标志,则相应的EVUTIL_AI_*标志也是他们的别名;如果操作系统定义了EAI_*错误,则相应的EVUTIL_EAI_*错误码等价于原始错误码。

#include  <event2/util.h>

 

#include  <sys/socket.h>

#include  <sys/types.h>

#include  <stdio.h>

#include  <string.h>

#include  <assert.h>

#include  <unistd.h>

 

evutil_socket_t  get_tcp_socket_for_host(const  char  *hostname, ev_uint16_t  port)

{

    char  port_buf[6];

    struct  evutil_addrinfo  hints;

    struct  evutil_addrinfo  *answer = NULL;

    int  err;

    evutil_socket_t  sock;

 

    /* Convert the port to decimal. */

    evutil_snprintf(port_buf,  sizeof(port_buf),  "%d",  (int)port);

 

    /* Build the hints to tell getaddrinfo howto act. */

    memset(&hints,  0,  sizeof(hints));

    hints.ai_family = AF_UNSPEC; /* v4 or v6 isfine. */

    hints.ai_socktype = SOCK_STREAM;

    hints.ai_protocol = IPPROTO_TCP; /* We wanta TCP socket */

    /* Only return addresses we can use. */

    hints.ai_flags = EVUTIL_AI_ADDRCONFIG;

 

    /* Look up the hostname. */

    err =evutil_getaddrinfo(hostname,  port_buf,  &hints,  &answer);

    if (err != 0) {

          fprintf(stderr, "Error whileresolving '%s': %s",

                  hostname,  evutil_gai_strerror(err));

          return -1;

    }

 

    /* If there was no error, we should have atleast one answer. */

    assert(answer);

    /* Just use the first answer. */

    sock = socket(answer->ai_family,

                  answer->ai_socktype,

                  answer->ai_protocol);

    if (sock < 0)

        return -1;

    if (connect(sock,  answer->ai_addr,  answer->ai_addrlen)) {

        /* Note that we're doing a blockingconnect in this function.

         * If this were nonblocking, we'd need to treatsome errors

         * (like EINTR and EAGAIN) specially.*/

        EVUTIL_CLOSESOCKET(sock);

        return -1;

    }

 

    return sock;

}

         这些函数都在event2/util.h中定义。

 

二:使用evdns_getaddrinfo进行非阻塞的域名解析

         常规的getaddrinfo接口,以及上面的evutil_getaddrinfo接口的一个主要问题,就是它们是阻塞的:当调用他们时,调用线程只能在请求DNS服务器时等待其返回结果。使用Libevent时,这种方式并不是合适的处理方式。

         所以,Libevent提供了一系列函数来进行非阻塞的DNS请求,并使用Libevent机制等待服务器返回结果。

typedef  void (*evdns_getaddrinfo_cb)(

    int  result, struct  evutil_addrinfo  *res,  void *arg);

struct  evdns_getaddrinfo_request;

 

struct  evdns_getaddrinfo_request  *evdns_getaddrinfo(

    struct  evdns_base  *dns_base,

    const  char  *nodename, const  char  *servname,

    const  struct  evutil_addrinfo *hints_in,

    evdns_getaddrinfo_cb  cb,  void *arg);

 

void  evdns_getaddrinfo_cancel(struct  evdns_getaddrinfo_request  *req);

         evdns_getaddrinfo函数类似于evutil_getaddrinfo,除了它在请求DNS服务器时不阻塞,它使用Libevent的底层DNS功能来进行域名解析。因为可能无法立即返回结果,因此需要提供一个evdns_getaddrinfo_cb类型的回调函数,并提供一个该回调函数的可选的用户提供的参数。

         另外,需要提供给evdns_getaddrinfo函数一个指向evdns_base的指针。该结构保存DNS解析器的状态和配置。详细信息参见下一节。

         如果evdns_getaddrinfo函数即刻成功,或者即刻失败,则其返回NULL。否则,它返回一个指向evdns_getaddrinfo_request结构的指针,可以使用该指针,调用evdns_getaddrinfo_cancel函数,在请求结束前的任意时刻取消DNS请求。

         注意,不管evdns_getaddrinfo函数返回NULL与否,也不管evdns_getaddrinfo_cancel是否调用,回调函数终将都会被调用。

         调用evdns_getaddrinfo函数时,对于nodename,servname以及hints参数,它都有自己的内部拷贝:在名称查找的过程中,无需保证他们一直存在。

#include  <event2/dns.h>

#include  <event2/util.h>

#include  <event2/event.h>

 

#include  <sys/socket.h>

 

#include  <stdio.h>

#include  <stdlib.h>

#include  <string.h>

#include  <assert.h>

 

int  n_pending_requests = 0;

struct  event_base  *base = NULL;

 

struct user_data {

    char  *name; /* the name we're resolving */

    int  idx;/* its position on the command line */

};

 

void  callback(int  errcode,  struct  evutil_addrinfo *addr,  void  *ptr)

{

    struct  user_data  *data = ptr;

    const  char  *name= data->name;

    if (errcode) {

        printf("%d. %s -> %s ",  data->idx, name,  evutil_gai_strerror(errcode));

    } else {

        struct  evutil_addrinfo  *ai;

        printf("%d. %s",data->idx, name);

        if (addr->ai_canonname)

            printf(" [%s]",addr->ai_canonname);

        puts("");

        for (ai = addr;  ai;  ai= ai->ai_next) {

            char  buf[128];

            const  char  *s= NULL;

            if (ai->ai_family == AF_INET) {

                struct  sockaddr_in  *sin = (struct  sockaddr_in *)ai->ai_addr;

                s = evutil_inet_ntop(AF_INET,  &sin->sin_addr,  buf,  128);

            } else if (ai->ai_family ==AF_INET6) {

                struct  sockaddr_in6  *sin6 = (struct  sockaddr_in6 *)ai->ai_addr;

                s = evutil_inet_ntop(AF_INET6,  &sin6->sin6_addr,  buf,  128);

            }

            if (s)

                printf("    -> %s ", s);

        }

        evutil_freeaddrinfo(addr);

    }

    free(data->name);

    free(data);

    if (--n_pending_requests == 0)

        event_base_loopexit(base,  NULL);

}

 

/* Take a list of domain namesfrom the command line and resolve them in

 * parallel. */

int main(int argc,  char **argv)

{

    int i;

    struct  evdns_base  *dnsbase;

 

    if (argc == 1) {

        puts("No addresses given.");

        return 0;

    }

    base = event_base_new();

    if (!base)

        return 1;

    dnsbase = evdns_base_new(base,  1);

    if (!dnsbase)

        return 2;

 

    for (i = 1; i < argc; ++i) {

        struct  evutil_addrinfo  hints;

        struct  evdns_getaddrinfo_request  *req;

        struct  user_data  *user_data;

        memset(&hints,  0,  sizeof(hints));

        hints.ai_family = AF_UNSPEC;

        hints.ai_flags = EVUTIL_AI_CANONNAME;

        /* Unless we specify a socktype, we'llget at least two entries for

         * each address: one for TCP and onefor UDP. That's not what we

         * want. */

        hints.ai_socktype = SOCK_STREAM;

        hints.ai_protocol = IPPROTO_TCP;

 

        if (!(user_data = malloc(sizeof(struct  user_data)))) {

            perror("malloc");

            exit(1);

        }

        if (!(user_data->name =strdup(argv[i]))) {

            perror("strdup");

            exit(1);

        }

        user_data->idx = i;

 

        ++n_pending_requests;

        req = evdns_getaddrinfo(

                          dnsbase,  argv[i],  NULL /* no service name given */,

                          &hints,  callback,  user_data);

        if (req == NULL) {

          printf("    [request for %s returnedimmediately] ", argv[i]);

          /* No need to free user_data ordecrement n_pending_requests; that

           * happened in the callback. */

        }

    }

 

    if (n_pending_requests)

      event_base_dispatch(base);

 

    evdns_base_free(dnsbase, 0);

    event_base_free(base);

 

    return 0;

}

         这些函数在event2/dns.h中声明。

 

三:创建并配置evdns_base

         在使用evdns进行非阻塞的DNS解析之前,需要对evdns_base进行配置。每一个evdns_base保存一个域名服务器的列表,以及DNS配置选项,它对正在进行的DNS请求进行跟踪

struct  evdns_base  *evdns_base_new(struct  event_base  *event_base,

      int  nitialize);

void  evdns_base_free(struct  evdns_base  *base,  int fail_requests);

         evdns_base_new函数成功时返回一个新的evdns_base结构,失败时返回NULL。如果参数initialize为1,则该函数会根据操作系统的默认值合理的配置evdns_base。如果该参数为0,则会将evdns_base置空,不包含任何域名服务器以及配置选项。

         当不再需要evdns_base的时候,可以使用evdns_base_free将其释放。如果其fail_requests参数为真,则在释放base之前,会使所有进行中的请求以错误码EVUTIL_EAI_CANCEL来调用他们的回调函数。

 

1:根据系统配置初始化evdns

         如果需要对evdns_base如何初始化进行更多的控制,可以将initialize参数设置为0调用evdns_base_new,并调用下列函数:

#define  DNS_OPTION_SEARCH  1

#define  DNS_OPTION_NAMESERVERS  2

#define  DNS_OPTION_MISC  4

#define  DNS_OPTION_HOSTSFILE  8

#define  DNS_OPTIONS_ALL  15

int  evdns_base_resolv_conf_parse(struct  evdns_base  *base,  int  flags,

                                 const  char  *filename);

 

#ifdef  WIN32

int  evdns_base_config_windows_nameservers(struct  evdns_base *);

#define  EVDNS_BASE_CONFIG_WINDOWS_NAMESERVERS_IMPLEMENTED

#endif

         evdns_base_resolv_conf_parse函数将会扫描resolv.conf格式的文件filename,并从该文件中读取flags中列出的选项。(关于resolv.conf文件的更多信息,可以参考本地的Unix操作手册)

         DNS_OPTION_SEARCH:使evdns从resolv.conf文件中读取domain和search字段以及ndots选项,并且根据这些字段决定用哪一个domain(如果有的话)来搜索不是全限定的主机名。

         DNS_OPTION_NAMESERVERS:该标志使evdns从resolv.conf文件中获取域名服务器。

         DNS_OPTION_MISC:该标志使evdns从resolv.conf文件中获取其他配置选项。

         DNS_OPTION_HOSTSFILE:该标志使evdns从/etc/hosts中读取主机列表,作为加载resolv.conf文件的一部分。

         DNS_OPTIONS_ALL:使evdns从resolv.conf文件中获取尽可能多的选项。

 

         在Windows上,因为没有resolv.conf文件指示域名服务器在哪,可以使用evdns_base_config_windows_nameservers函数从注册表(或者NetworkParams,或者其他地方)中读取所有的域名服务器。

 

2:resolv.conf文件格式

         resolv.conf格式的文件是一个文本文件,每一行要么是空行,要么是以“#”开头的注释,要么是包含若干参数的关键字。支持的关键字如下:

nameserver:后面跟着一个域名服务器的IP地址。作为扩展,Libevent允许为域名服务器指定一个非标准的端口,使用IP:Port或者[IPv6]:port这种格式。

         domain:本地域名

         search:在解析本地主机名时,需要搜索的名字列表。包含少于ndots个点的名字会被认为是本地的,而且如果不能正确解析的话,则会在这些域名中进行查找。比如,如果search为example.com,并且ndots为1的话,如果用户需要解析www,则会将其当做www.example.com

 

         options:一些以空格分割的选项。每一个选项要么是一个空字符串,要么是option:value格式。支持下列options:

         ndots:INTEGER,用来配置search,参照上面“search”,默认值为1.

         timeout:FLOAT,在请求DNS服务器时,等待的超时时间,以秒数为单位。默认为5秒。

         max-timeouts:INT,DNS服务器超时几次才认为DNS服务器宕机,默认为3次。

         max-inflight:INT:最多允许多少个未决的DNS请求(如果请求数过多,则多余的请求会阻塞,直到先前的请求应答了或者超时了),默认为64.

         attempts:INT,放弃之前,尝试发送DNS请求的次数,默认为3.

         randomize-case:INT,如果非零,evdns会为发出的DNS请求设置随机的事务ID,并且确认回应具有同样的随机事务ID值。这种称作“0x20 hack”的机制可以在一定程度上阻止对DNS的简单激活事件攻击。这个选项的默认值是1。(存疑)

         bind-to:ADDRESS,如果提供了该选项,则在发送请求到域名服务器时,会绑定到给定的地址。对于Libevent2.0.4-alpha版本,该选项只应用在后面的域名服务器选项上。

         initial-probe-timeout:FLOAT,当发现一个域名服务器down掉时,以指数抵减的频率探测其是否启动。该选项配置一系列超时时间的第一个超时时间,以秒为单位,默认为10。

         getaddrinfo-allow-skew:FLOAT,当evdns_getaddrinfo同时请求IPv4地址以及IPv6地址时,它会在分离的DNS请求报文中进行,因为一些服务器无法在一个包中同时处理两种请求。一旦它对于一种地址类型有了回应,它会等待一段时间看另一个答案是否已经到达。该选项配置这段时间的长度,以秒为单位,默认为3秒。

        

         不识别的符号和选项都会被忽略。

 

3:手动配置evdns

         如果需要更加细粒度的控制evdns的行为,可以使用下列函数:

int  evdns_base_nameserver_sockaddr_add(struct  evdns_base  *base,

                                 const  struct  sockaddr *sa,  ev_socklen_t  len,

                                 unsigned  flags);

int  evdns_base_nameserver_ip_add(struct  evdns_base  *base,

                                 const  char  *ip_as_string);

int  evdns_base_load_hosts(struct  evdns_base  *base,  const  char *hosts_fname);

 

void  evdns_base_search_clear(struct  evdns_base  *base);

void  evdns_base_search_add(struct  evdns_base  *base,  const char  *domain);

void  evdns_base_search_ndots_set(struct  evdns_base  *base,  int ndots);

 

int  evdns_base_set_option(struct  evdns_base  *base,  const  char  *option,

    const  char  *val);

 

int  evdns_base_count_nameservers(struct  evdns_base  *base);

         evdns_base_nameserver_sockaddr_add函数通过socket地址的形式,evdns_base添加一个域名服务器。flags参数目前是被忽略的,而且为了向前兼容性应该置为0。该函数成功是返回0,失败时返回负数。

         evdns_base_nameserver_ip_add函数也是向evdns_base添加域名服务器。不过它是以字符串的形式添加,该字符串可以是一个IPv4地址,一个IPv6地址,一个有端口号的IPv4地址(IPv4:Port),或者一个有端口号的IPv6地址([IPv6]:port)。该函数成功时返回0,失败时返回负数。

         evdns_base_load_hosts函数从hosts_name中加载一个主机文件(类似于/etc/hosts的格式)。该函数成功时返回0,失败时返回负数。

         evdns_base_search_clear函数从evdns_base中清除所有当前的search后缀(就像search选项中配置的那样);evdns_base_search_add函数则增加一个后缀。

         evdns_base_set_option函数向evdns_base添加一个选项key和该选项的值value,key和value都是字符串的形式。(2.0.3版本之前,选项名后面必须有一个冒号)

         解析一系列的配置文件之后,如果希望看到是否已经添加了域名服务器,可以使用evdns_base_count_nameservers查看有多少个域名服务器。

 

4:库端的配置

         Libevent提供了一对函数可以对evdns模块进行库级别的设置:

typedef  void (*evdns_debug_log_fn_type)(int  is_warning,  const  char  *msg);

void  evdns_set_log_fn(evdns_debug_log_fn_type  fn);

void  evdns_set_transaction_id_fn(ev_uint16_t  (*fn)(void));

         由于历史的原因,evdns子系统具有其自己独立的日志功能;可以使用evdns_set_log_fn函数添加回调函数,对消息进行处理。

         出于安全性的考虑,evdns需要一个好的随机数源:在使用“0x20 hack”时,它被用来获取难以被猜中的事务ID,从而用来随机化查询(参考“randomize-case”选项)。然而老版本的Libevent,并没有提供一个安全的RNG。可以通过调用evdns_set_transaction_id_fn ,并向其提供一个能够返回难以预测的2字节无符号整数的函数,来为evdns设置一个更好的随机数产生器

         在Libevent2.0.4-alpha及其之后的版本,Libevent使用自己的内部的安全RNG;所以evdns_set_transaction_id_fn函数不在起作用。

 

四:底层DNS接口

         有时需要以比evdns_getaddrinfo能更加细粒度的控制进行特别的DNS请求。Libevent提供了相应的接口。

 

1:缺少的特性:

         目前,Libevent的DNS缺少一些其他底层DNS系统所能提供的特性,比如对于任意请求类型和TCP请求的支持。如果需要那些evdns所不支持的特性,希望您能够贡献一个补丁。或者也可以考虑更加全能的DNS库比如c-ares。

 

#define  DNS_QUERY_NO_SEARCH /* ... */

 

#define  DNS_IPv4_A         /* ... */

#define  DNS_PTR            /* ... */

#define  DNS_IPv6_AAAA      /* ... */

 

typedef  void (*evdns_callback_type)(int  result,  char  type, int  count,

    int  ttl, void  *addresses,  void  *arg);

 

struct  evdns_request  *evdns_base_resolve_ipv4(struct  evdns_base  *base,

    const  char  *name, int  flags,  evdns_callback_type callback,  void  *ptr);

struct  evdns_request  *evdns_base_resolve_ipv6(struct  evdns_base  *base,

    const  char  *name, int  flags,  evdns_callback_type callback,  void  *ptr);

struct  evdns_request  *evdns_base_resolve_reverse(struct  evdns_base  *base,

    const  struct  in_addr *in,  int  flags, evdns_callback_type  callback,

    void  *ptr);

struct  evdns_request  *evdns_base_resolve_reverse_ipv6(

    struct  evdns_base  *base,  const struct  in6_addr *in,  int  flags,

    evdns_callback_type  callback,  void  *ptr);

         这些解析函数为一个特殊的记录发起DNS请求。每一个函数都使用evdns_base来进行请求,函数参数还包括需要查询的源(既可以是正向查找时的主机名,也可以是反向查找时的一个地址),一系列决定如何进行查找的标志集合,当查找结束时调用的回调函数,以及一个用户提供的传递给回调函数的参数的指针。

         flags参数可以是0,或者是DNS_QUERY_NO_SEARCH ,DNS_QUERY_NO_SEARCH 表示在原始搜索失败的时候,明确禁止在search列表中进行搜索。DNS_QUERY_NO_SEARCH 标志对于反向查询没有意义,因为反向查询不会进行搜索。

         当请求完成时(成功或失败),就会调用回调函数。回调函数的参数有:result表明成功或者是一个错误码(参看下面的DNS错误码),一个记录类型(DNS_IPv4_A,DNS_IPv6_AAAA或 DNS_PTR之一),addresses是记录的个数,ttl秒数,地址本身以及用户提供的参数指针。

         如果发生了错误,则回调函数的addresses参数为NULL。如果没有发生错误,对于PTR记录来说,它是一个NULL结尾的字符串。对于IPv4记录来说,它是一个网络字节序的4字节的数组。对于IPv6来说,则是一个网络字节序的16字节数组。(注意,即使没有错误,addresses的数量也可以是0。比如名字存在,但是却没有请求类型的记录)

        

         可以传递给回调函数的错误码如下:

错误码

意义

DNS_ERR_NONE

没有错误发生

DNS_ERR_FORMAT

服务器无法理解该请求

DNS_ERR_SERVERFAILED

服务器发生了内部错误

DNS_ERR_NOTEXIST

对于给定的name,没有record

DNS_ERR_NOTIMPL

服务器无法理解该类型的请求

DNS_ERR_REFUSED

因策略设置,服务器决绝该请求

DNS_ERR_TRUNCATED

DNS 记录不适合UDP报文

DNS_ERR_UNKNOWN

未知的内部错误

DNS_ERR_TIMEOUT

请求超时

DNS_ERR_SHUTDOWN

用户要求关闭evdns系统

DNS_ERR_CANCEL

用户要求取消这次请求

DNS_ERR_NODATA

虽然收到了响应,但是其中却没有包含答案

         可以使用下面的接口将错误码转换为可读的字符串:

const  char  *evdns_err_to_string(int err);

 

         每一个解析函数都返回一个不透明的evdns_request结构,可以使用该结构,在回调函数调用之前的任一时刻取消请求:

void  evdns_cancel_request(struct  evdns_base  *base, struct  evdns_request  *req);

         使用该函数取消一个请求,会使得回调函数以DNS_ERR_CANCEL为结果码被调用。

 

五:挂起DNS客户端操作、改变域名服务器

         有时希望能够在不太影响正在进行的DNS请求的情况下,对DNS子系统重新配置或者关闭。

int  evdns_base_clear_nameservers_and_suspend(struct evdns_base  *base);

int  evdns_base_resume(struct  evdns_base  *base);

         如果在evdns_base上调用函数evdns_base_clear_nameservers_and_suspend,则所有的域名服务器都会被删除,并且未决的请求会被保留,直到重新添加域名服务器并且调用evdns_name_resume为止。

         这些函数成功时返回0,失败时返回-1。

 

六:DNS服务器接口

         Libevent提供了简单的功能实现一个普通的DNS服务器,并且对UDP的DNS请求进行应答。本节的内容需要你熟悉一些DNS协议。

 

1:创建和关闭DNS服务器

struct  evdns_server_port  *evdns_add_server_port_with_base(

    struct  event_base *base,

    evutil_socket_t  socket,

    int  flags,

    evdns_request_callback_fn_type  callback,

    void  *user_data);

 

typedef  void (*evdns_request_callback_fn_type)(

    struct  evdns_server_request  *request,

    void  *user_data);

 

void  evdns_close_server_port(struct  evdns_server_port  *port);

         调用evdns_add_server_port_with_base开始监听DNS请求。该函数的参数有:一个处理事件的event_base,一个用来监听的UDP socket,flags变量(目前总是为0);当收到新的DNS请求时调用的回调函数;一个用户提供的回调函数的参数指针。该函数返回一个新的evdns_server_port对象。

         当DNS 服务器的工作完成时,可以将该对象传递给evdns_close_server_port函数进行关闭。

 

2:检测DNS请求

         不幸的是,Libevent目前没有为通过可编程的接口来获取DNS请求提供一个很好的方式。相反的,需要包含event2/dns_struct.h,并且手动检测evdns_server_quest结构。

         将来版本的Libevent如果能提供一个更好的操作方式的话就好了。

struct  evdns_server_request {

        int  flags;

        int  nquestions;

        struct  evdns_server_question  **questions;

};

#define  EVDNS_QTYPE_AXFR  252

#define  EVDNS_QTYPE_ALL   255

struct  evdns_server_question {

        int  type;

        int  dns_question_class;

        char  name[1];

};

         请求的flags字段包含了请求中设置的DNS标志;nquestions是请求中包含的问题数;questions是指向结构体evdns_server_question的指针数组。每一个evdns_server_question都包含了请求的资源类型(参考下面的EVDNS_*_TYPE宏),请求的类型(典型的是EVDNS_CLASS_INET)以及请求主机名的名称。

 

int  evdns_server_request_get_requesting_addr(struct evdns_server_request  *req,

        struct  sockaddr  *sa,  int addr_len);

         有时需要知道某特定的DNS请求来自何方。可以通过调用函数evdns_server_request_get_requestion_addr函数来获得。需要传递一个足够大小的sockaddr来保存地址:建议使用sockaddr_storage结构。

 

3:响应DNS请求

         DNS服务器每收到一个请求,都会将该请求,连同用户提供的指针,一起传递到回调函数中。该回调函数必须要么能响应请求或者忽略请求,要么保证该请求最终会被应答或者忽略。

         在应答请求之前,可以向应答中添加一个或多个答案:

int  evdns_server_request_add_a_reply(struct  evdns_server_request  *req,

    const  char  *name, int  n,  const void  *addrs,  int  ttl);

int  evdns_server_request_add_aaaa_reply(struct  evdns_server_request  *req,

    const  char  *name, int  n,  const void  *addrs,  int  ttl);

int  evdns_server_request_add_cname_reply(struct  evdns_server_request  *req,

    const  char  *name, const  char  *cname, int  ttl);

         上述函数会添加一个单独的RR(A,AAAA类型或者CNAME)到req请求的应答中。每个函数中,参数name就是要添加到答案中的主机名,ttl就是答案中的生存时间秒数。对于A以及AAAA记录来说,n就是需要添加的地址个数,addrs是指向原始地址的指针,它要么是在A记录中的4字节的IPv4地址,要么是AAAA记录中的16字节的IPv6地址。

         这些函数成功时返回0,失败时返回-1。

 

int  evdns_server_request_add_ptr_reply(struct  evdns_server_request  *req,

    struct  in_addr  *in,  const  char  *inaddr_name, const  char  *hostname,

    int  ttl);

         该函数向请求的应答中添加一个PTR记录。req和ttl参数类似于上面的参数。必须以一个in(IPv4地址)或者inaddr_name(在.arpa域中的地址)表明要应答的那个地址。hostname参数就是PTR查询的答案。

#define  EVDNS_ANSWER_SECTION  0

#define  EVDNS_AUTHORITY_SECTION  1

#define  EVDNS_ADDITIONAL_SECTION  2

 

#define  EVDNS_TYPE_A       1

#define  EVDNS_TYPE_NS      2

#define  EVDNS_TYPE_CNAME   5

#define  EVDNS_TYPE_SOA     6

#define  EVDNS_TYPE_PTR    12

#define  EVDNS_TYPE_MX     15

#define  EVDNS_TYPE_TXT    16

#define  EVDNS_TYPE_AAAA   28

 

#define  EVDNS_CLASS_INET   1

 

int  evdns_server_request_add_reply(struc tevdns_server_request  *req,

    int  section, const  char  *name,  int type,  int  dns_class,  int  ttl,

    int  datalen, int  is_name,  const  char *data);

         该函数向req请求的DNS应答添加任意的RR。section参数表明需要添加哪一部分,而且该值必须是EVDNS_*_SECTION的其中之一。name参数是RR中的name字段。type参数是RR中的type字段,而且可能的话应该是EVDNS_TYPE_*的其中之一。dns_class参数是RR中的class字段,而且一般应该是EVDNS_CLASS_INET。ttl参数是RR中的存活时间字段。RR中的rdata和rdlength字段将会由data中的datalen个字节产生。如果is_name为true,则data将会编码为一个DNS名,否则,它将按照字面意思包含到RR中。

 

int  evdns_server_request_respond(struct  evdns_server_request  *req,  int err);

int  evdns_server_request_drop(struct  evdns_server_request  *req);

         evdns_server_request_respond函数发送一个DNS回应,包含所有RRs,以及错误码err。如果收到一个不想应答的请求,可以通过调用evdns_server_request_drop函数忽略它,从而可以释放所有相关内存以及记录结构。

 

#define  EVDNS_FLAGS_AA 0x400

#define  EVDNS_FLAGS_RD 0x080

 

void  evdns_server_request_set_flags(struct  evdns_server_request  *req,  int flags);

         如果需要在应答消息中设置任何标志,可以在发送应答之前的任意时间调用该函数。

 

4:DNS服务器例子

#include <event2/dns.h>

#include<event2/dns_struct.h>

#include <event2/util.h>

#include <event2/event.h>

 

#include <sys/socket.h>

 

#include <stdio.h>

#include <string.h>

#include <assert.h>

 

/* Let's try binding to5353.  Port 53 is more traditional, buton most

   operating systems it requires rootprivileges. */

#define LISTEN_PORT 5353

 

#define LOCALHOST_IPV4_ARPA"1.0.0.127.in-addr.arpa"

#define LOCALHOST_IPV6_ARPA("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0."        

                            "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa")

 

const ev_uint8_tLOCALHOST_IPV4[] = { 127, 0, 0, 1 };

const ev_uint8_tLOCALHOST_IPV6[] = { 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,1 };

 

#define TTL 4242

 

/* This toy DNS server callbackanswers requests for localhost (mapping it to

   127.0.0.1 or ::1) and for 127.0.0.1 or ::1(mapping them to localhost).

 */

void server_callback(structevdns_server_request *request, void *data)

{

    int i;

    int error=DNS_ERR_NONE;

    /* We should try to answer all thequestions.  Some DNS servers don't do

       this reliably, though, so you shouldthink hard before putting two

       questions in one request yourself. */

    for (i=0; i < request->nquestions;++i) {

        const struct evdns_server_question *q =request->questions[i];

        int ok=-1;

        /* We don't use regular strcasecmphere, since we want a locale-

           independent comparison. */

        if (0 ==evutil_ascii_strcasecmp(q->name, "localhost")) {

            if (q->type == EVDNS_TYPE_A)

                ok =evdns_server_request_add_a_reply(

                       request, q->name, 1,LOCALHOST_IPV4, TTL);

            else if (q->type ==EVDNS_TYPE_AAAA)

                ok =evdns_server_request_add_aaaa_reply(

                       request, q->name, 1,LOCALHOST_IPV6, TTL);

        } else if (0 ==evutil_ascii_strcasecmp(q->name, LOCALHOST_IPV4_ARPA)) {

            if (q->type == EVDNS_TYPE_PTR)

                ok =evdns_server_request_add_ptr_reply(

                       request, NULL,q->name, "LOCALHOST", TTL);

        } else if (0 ==evutil_ascii_strcasecmp(q->name, LOCALHOST_IPV6_ARPA)) {

            if (q->type == EVDNS_TYPE_PTR)

                ok =evdns_server_request_add_ptr_reply(

                       request, NULL,q->name, "LOCALHOST", TTL);

        } else {

            error = DNS_ERR_NOTEXIST;

        }

        if (ok<0 &&error==DNS_ERR_NONE)

            error = DNS_ERR_SERVERFAILED;

    }

    /* Now send the reply. */

    evdns_server_request_respond(request,error);

}

 

int main(int argc, char **argv)

{

    struct event_base *base;

    struct evdns_server_port *server;

    evutil_socket_t server_fd;

    struct sockaddr_in listenaddr;

 

    base = event_base_new();

    if (!base)

        return 1;

 

    server_fd = socket(AF_INET, SOCK_DGRAM, 0);

    if (server_fd < 0)

        return 2;

    memset(&listenaddr, 0, sizeof(listenaddr));

    listenaddr.sin_family = AF_INET;

    listenaddr.sin_port = htons(LISTEN_PORT);

    listenaddr.sin_addr.s_addr = INADDR_ANY;

    if (bind(server_fd, (structsockaddr*)&listenaddr, sizeof(listenaddr))<0)

        return 3;

 

    server = evdns_add_server_port_with_base(base,server_fd, 0,

                                            server_callback, NULL);

 

    event_base_dispatch(base);

 

    evdns_close_server_port(server);

    event_base_free(base);

 

    return 0;

}

 

 

七:废弃的DNS接口

void  evdns_base_search_ndots_set(struct  evdns_base  *base, const  int  ndots);

int  evdns_base_nameserver_add(struct  evdns_base  *base,  

                   unsigned  long int  address);

void  evdns_set_random_bytes_fn(void (*fn)(char *,  size_t));

 

struct  evdns_server_port  *evdns_add_server_port(evutil_socket_t  socket,

    int  flags, evdns_request_callback_fn_type  callback,  void  *user_data);

         调用evdns_base_search_ndots_set函数等价于以ndots选项调用函数evdns_base_set_option。

         函数evdns_base_nameserver_add类似于evdns_base_nameserver_ip_add,除了它只能添加IPv4地址的域名服务器。IPv4地址是网络字节序的4字节数组。

         在Libevent2.0.1-alpha之前,无法为一个DNS服务器指定一个event base,只能使用evdns_add_server_port函数,它使用默认的event_base。

         在 Libevent2.0.1-alpha到2.0.3-alpha之间,需要使用evdns_set_random_bytes_fn函数来指定一个产生随机数的函数,而不是使用evdns_set_transaction_id_fn。目前它不再有任何作用,现在Libevent有了自己的安全的RNG。

         DNS_QUERY_NO_SEARCH标志也被称为DNS_NO_SEARCH。

         在Libevent2.0.1-alpha之前,没有单独的evdns_base记号:所有evdns子系统中的信息都是全局存储的,而且操作它的函数没有evdns_base参数。这些函数全都是已经废弃了,他们在event2/dns_compat.h中声明。他们通过一个全局的evdns_base来实现;可以通过调用Libevent 2.0.3-alpha引进的函数evdns_get_global_base来访问该evdns_base。

Current function

Obsolete global-evdns_base version

event_base_new()

evdns_init()

evdns_base_free()

evdns_shutdown()

evdns_base_nameserver_add()

evdns_nameserver_add()

evdns_base_count_nameservers()

evdns_count_nameservers()

evdns_base_clear_nameservers_and_suspend()

evdns_clear_nameservers_and_suspend()

evdns_base_resume()

evdns_resume()

evdns_base_nameserver_ip_add()

evdns_nameserver_ip_add()

evdns_base_resolve_ipv4()

evdns_resolve_ipv4()

evdns_base_resolve_ipv6()

evdns_resolve_ipv6()

evdns_base_resolve_reverse()

evdns_resolve_reverse()

evdns_base_resolve_reverse_ipv6()

evdns_resolve_reverse_ipv6()

evdns_base_set_option()

evdns_set_option()

evdns_base_resolv_conf_parse()

evdns_resolv_conf_parse()

evdns_base_search_clear()

evdns_search_clear()

evdns_base_search_add()

evdns_search_add()

evdns_base_search_ndots_set()

evdns_search_ndots_set()

evdns_base_config_windows_nameservers()

evdns_config_windows_nameservers()

         当且仅当存在evdns_config_windows_nameservers()函数时,EVDNS_CONFIG_WINDOWS_NAMESERVERS_IMPLEMENTED宏才被定义。

 

原文:http://www.wangafu.net/~nickm/libevent-book/Ref9_dns.html

原文地址:https://www.cnblogs.com/gqtcgq/p/7247252.html