libevent学习笔记【使用篇】——DNS域名解析 evdns

Libevent 提供了一些用于解析DNS域名的API, 以及一些用于实现DNS Server的接口。

可移植的阻塞型域名解析

Libevent 提供了标准库函数getaddrinfo的可移植版本,用于让阻塞性域名解析应用于可移植程序。(毕竟有些平台并不支持getaddrinfo函数,或者该函数相对于libevent的替代接口表现更差)。

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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • notename: 节点名称,如IP地址(127.0.0.1, ::1) 或域名www.example.com 等,如果值为控偶,则表示localhost(默认情况下),或者·any·(EVUTIL_AI_PASSIVE)被设置的情况下
  • servname: 服务名,如https或者端口号。如果没有指定servname,则*res中的port会被设置为0.
  • hints: 参数提供了与解析规则相关的参数。
  • res: 返回的解析结果列表。 
    notename和servname至少要指定一个

在解析规则hints中,包含了成员ai_flag,它指定了如何进行lookup(查询)。下面是一些常用的值:

  • EVUTIL_AI_PASSIVE: 指明我们将用这个地址来进行listening 而不是connecting。(一般当 nodename为NULL时才有用。当nodename为NULL时, NULL nodename在connecting时表示localhost;在listening时表示ANY)
  • EVUTIL_AI_CANONNAME: 该标志位设置了表示:我们将在ai_canonname字段中显示canonical名字。
  • EVUTIL_AI_NUMERICHOST: 表示只解析(数字形式)IPv4或者IPv6地址。 当nodename被指定为名字解析时,将返回EVUTIL_EAI_NONAME错误。
  • EVUTIL_AI_V4MAPPED: 如果ai_family字段被指定为AF_INET6, 而又解析不到IPv6的地址,那么这个解析到的IPv4的地址将作为IPv6地址的映射返回。(取决于系统是否支持。)
  • EVUTIL_AI_ALL: 如果这个flag以及EVUTIL_AI_V4MAPPED都被设置了,那么不管有没有解析到IPv6的地址, 在解析结果中的IPv4地址都会作为4-mapped IPv6地址形式被保存。

如果evutil_getaddrinfo解析成功了,则它会分配一个节点为evutil_addrinfo structures类型的链表res,用于保存每一个解析到的地址。这些内存是从堆中分配的,可以通过evutil_freeaddrinfo释放内存。 
如果evutil_getaddrinfo解析失败了,则它会返回一个错误码(如EVUTIL_EAI_ADDRFAMILY)可以通过evutil_gai_strerror解析。

// Example

#include 

#include 
#include 
#include 
#include 
#include 
#include 

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 how to act. */
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC; /* v4 or v6 is fine. */
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP; /* We want a 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 while resolving '%s': %s",
                  hostname, evutil_gai_strerror(err));
          return -1;
    }

    /* If there was no error, we should have at least 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 blocking connect in this function.
         * If this were nonblocking, we'd need to treat some errors
         * (like EINTR and EAGAIN) specially. */
        EVUTIL_CLOSESOCKET(sock);
        return -1;
    }

    return sock;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

evdns_getaddrinfo: 非阻塞的域名解析

不管是标准的getaddrinfo还是我们前面介绍的evutil_getaddrinfo都存在一个问题:它们都是阻塞的。 
然而,我们一般讲Libevent应用于非阻塞环境中,因而最好有一个非阻塞的域名解析接口。 
庆幸的是,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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

从函数原型中, 不难看出,evdns_getaddrinfo的使用方法与其他使用回调函数实现非阻塞的函数类似的。其参数的作用可以参考阻塞型的evutil_getaddrinfo。 
evdns_getaddrinfo有三种返回值:

  • NULL: 调用失败,或者立即得到了结果。
  • 返回指向evdns_getaddrinfo_request类型的指针: 可以通过该指针取消DNS解析请求。
//Example:

#include 
#include 
#include 

#include 

#include 
#include 
#include 
#include 

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\n", 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\n", s);
        }
        evutil_freeaddrinfo(addr);
    }
    free(data->name);
    free(data);
    if (--n_pending_requests == 0)
        event_base_loopexit(base, NULL);
}

/* Take a list of domain names from 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'll get at least two entries for
         * each address: one for TCP and one for 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 returned immediately]\n", argv[i]);
          /* No need to free user_data or decrement 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115

创建和配置evdns_base

在使用evdns_getaddrinfo之前,我们需要先配置一个evdns_base用于保存nameservers列表、DNS 配置选项、追踪活跃的和准备发射的DNS请求。

struct evdns_base *evdns_base_new(struct event_base *event_base,
       int initialize);
void evdns_base_free(struct evdns_base *base, int fail_requests);
  • 1
  • 2
  • 3

从系统配置(文件)初始化evdns

Libevent提供了根据配置文件来初始化evdns的方法:

#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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

evdns_base_resolv_conf_parse()函数扫描resolv.conf格式的文件filename,从中读取flags指示的选项(关于resolv.conf文件的更多信息,请看Unix手册)。

  • DNS_OPTION_SEARCH: 请求从resolv.conf文件读取domain和search字段以及ndots选项,使用它们来确定使用哪个域(如果存在)来搜索不是全限定的主机名。
  • DNS_OPTION_NAMESERVERS: 请求从resolv.conf中读取名字服务器地址。
  • DNS_OPTION_MISC: 请求从resolv.conf文件中读取其他配置选项。
  • DNS_OPTION_HOSTSFILE: 请求从/etc/hosts文件读取主机列表。
  • DNS_OPTION_ALL: 请求从resolv.conf文件获取尽量多的信息。

手动配置 evdns

Libevent 同样也提供了手动配置evdns的方法,以便更加灵活地运用dvdns

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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 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函数:解析一系列的配置文件之后,如果希望看到是否已经添加了域名服务器,可以使用这个函数来查看有多少个域名服务器。

库端的配置

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));
  • 1
  • 2
  • 3

由于历史的原因,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函数不在起作用。

evdns 底层接口

Libevent提供了更加底层的接口,以便用来控制进行特别的DNS请求。

#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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

这些解析函数为一个特殊的记录发起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_NOTEXIT: 对于给定的name,美誉record
  • DNS_ERR_NOTIMPL: 服务器无法理解该类型的请求
  • DNS_ERR_REFUSED: 因策略设置,服务器决绝该请求
  • DNS_ERR_UNKNOWN: 未知的内部错误
  • DNS_ERR_TIMEOUT:请求超时
  • DNS_ERR_SHUTDOWN: 用户要求关闭evdns系统
  • DNS_ERR_CANCEL: 用户要求取消这次请求
  • DNS_ERR_NODATA: 虽然受到了响应,但是其中却没有包含答案。

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

const  char  *evdns_err_to_string(int err);
  • 1

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

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

使用该函数取消一个请求,会使得回调函数以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);
  • 1
  • 2

如果在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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

调用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];
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

请求的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);
  • 1
  • 2

有时需要知道某特定的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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

上述函数会添加一个单独的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);
  • 1
  • 2
  • 3

该函数向请求的应答中添加一个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(struct evdns_server_request *req,
    int section, const char *name, int type, int dns_class, int ttl,
    int datalen, int is_name, const char *data);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

该函数向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);
  • 1
  • 2

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);
  • 1
  • 2
  • 3
  • 4

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

4 DNS服务器的例子

#include 
#include 
#include 
#include 

#include 

#include 
#include 
#include 

/* Let's try binding to 5353.  Port 53 is more traditional, but on most
   operating systems it requires root privileges. */
#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_t LOCALHOST_IPV4[] = { 127, 0, 0, 1 };
const ev_uint8_t LOCALHOST_IPV6[] = { 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,1 };

#define TTL 4242

/* This toy DNS server callback answers 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(struct evdns_server_request *request, void *data)
{
    int i;
    int error=DNS_ERR_NONE;
    /* We should try to answer all the questions.  Some DNS servers don't do
       this reliably, though, so you should think 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 strcasecmp here, 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, (struct sockaddr*)&listenaddr, sizeof(listenaddr))<0)
        return 3;
    /*The server will hijack the event loop after receiving the first request if the socket is blocking*/
    if(evutil_make_socket_nonblocking(server_fd)<0)
        return 4;
    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;
    }

你可能感兴趣的:(c++,c++开源库)