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:该标志表明,解析的地址将用来被动监听,而不是主动建链。一般情况下两者没有区别,除非nodename为NULL:对于主动建链来说,一个为NULL的nodename意味着本地地址(127.0.0.1或::1),而对于被动监听来说,一个为NULL的nodename意味着ANY(0.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\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 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]\n", 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

Libevent:11使用Libevent的DNS上层和底层功能的更多相关文章

  1. 【转】libevent和基于libevent的网络编程

    转自: http://www.cnblogs.com/nearmeng/p/4043548.html 1 libevent介绍和安装 介绍 libevent是一个轻量级的基于事件驱动的高性能的开源网络 ...

  2. libevent和基于libevent的网络编程

    1 libevent介绍和安装 介绍 libevent是一个轻量级的基于事件驱动的高性能的开源网络库,并且支持多个平台,对多个平台的I/O复用技术进行了封装,当我们编译库的代码时,编译的脚本将会根据O ...

  3. Android 从上层到底层-----app层

    CPU:RK3288 系统:Android 5.1 功能:上层 app 控制 led 亮灭 开发板:Firefly RK3288 MainActivity.java package com.aaron ...

  4. Android 从上层到底层-----jni层

    CPU:RK3288 系统:Android 5.1 功能:上层 app 控制 led 亮灭 开发板:Firefly RK3288 led_jni.h path:hardware/rockchip/fi ...

  5. Android 从上层到底层-----hal层

    CPU:RK3288 系统:Android 5.1 功能:上层 app 控制 led 亮灭 开发板:Firefly RK3288 led_hal.c path:hardware/rockchip/fi ...

  6. Android 从上层到底层-----kernel层

    CPU:RK3288 系统:Android 5.1 功能:上层 app 控制 led 亮灭 开发板:Firefly RK3288 1.在dts文件中增加 led 设备 path:kernel/arch ...

  7. Linux、Android系统调用从上层到底层的调用路径浅析

    参考: https://blog.csdn.net/liuhangtiant/article/details/85149369 http://blog.sina.com.cn/s/blog_79433 ...

  8. 第11.13节 Python正则表达式的转义符”\”功能介绍

    为了支持特殊元字符在特定场景下能表示自身而不会被当成元字符进行匹配出来,可以通过字符集或转义符表示方法来表示,字符集表示方法前面在<第11.4节 Python正则表达式搜索字符集匹配功能及元字符 ...

  9. libevent使用<一> libevent导入项目

    最近做mysql代理层读写分离,发现在C,C++领域libevent很厉害的样子. 1. 安装libevent linux下源码安装或者直接yum安装. libevent只是一套对一些底层技术的封装, ...

随机推荐

  1. LINUX对于未安装的软件包的查看

    查看的前提是您有一个.rpm 的文件,也就是说对既有软件file.rpm的查看等: 1.查看一个软件包的用途.版本等信息: 语法: rpm -qpi file.rpm 举例: [root@localh ...

  2. tmux使用教程

    1.安装 2.操作 如何操作快捷键呢? 比如新建一个窗口的命令是:ctrl+b+c 那么,先按住ctrl不放,接着按下b键,然后ctrl和b键都完全松开后,再立马按下c键. 3.使用命令行 tmux ...

  3. Echarts 的简单使用

    http://echarts.baidu.com/index.html 直接用script引入从官网下载的echarts.js文件 官网的文件有几种版本的,按需下载即可,注意精简版的只显示折线.圆柱等 ...

  4. Flask中的session机制

    cookie和sessioncookie:网站中,http请求是无状态的,第一次和服务器连接后并且登陆成功后,第二次请求服务器依然不能知道当前请求是哪个用户.cookie的出现就是解决了改问题,第一次 ...

  5. python基础--socket套接字、粘包问题

    本地回环地址:127.0.0.1 简易版服务端: import socket ​ server = socket.socket() # 就比如买了一个手机 server.bind(("127 ...

  6. day38 09-Spring类的完整生命周期及后处理Bean

    可以配置Bean的这个类的初始化和销毁的方法. 如何销毁这个bean?销毁必须得手动地关闭掉容器才行.而且销毁必须是在scope="singleton"下才有效.因为如果你scop ...

  7. 集训队日常训练20180513-DIV1

    A.3132 给一个有向图,问能否从任意点出发都能进入一个环中. 深搜. #include<bits/stdc++.h> using namespace std; ; vector< ...

  8. Leetcode63.Unique Paths II不同路径2

    一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为"Start" ). 机器人每次只能向下或者向右移动一步.机器人试图达到网格的右下角(在下图中标记为" ...

  9. XtraBackup构建MySQL主从环境的方法

    环境:HE3主库,HE1从库HE1:192.168.1.248HE3:192.168.1.250从库my.cnf加入以下参数并重启数据库:read_only=1log_slave_updates=1( ...

  10. CSS(中)篇

    1.1行高 行高属于文字的属性 行高=文字大小+上间距+下间距(默认行高=18px) 行高的作用: 设置文字垂直方向中有距离 文字垂直居中(行高=容器的高度) 影响行高的因素: 文字大小可以改变行高 ...