zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    Mac使用笔记(二)
    AJAX tooltip by jQuery UI Widget and MVC3
    MVC4的bundling功能简介
    Mac使用笔记
    浅析ASP.Net Web API的Formatter
    浅析ASP.net Web API的Model验证(使用MVC4框架的Web API须谨慎)
    2012年读过的最好的书
    SQLite在.net下的使用方法
    C#也允许函数默认参数
    chrome不支持对opener方法的调用?
  • 原文地址:https://www.cnblogs.com/gqtcgq/p/7247252.html
Copyright © 2011-2022 走看看