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:该标志表明,解析的地址将用来被动监听,而不是主动建链。一般情况下两者没有区别,除非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 |
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上层和底层功能的更多相关文章
- 【转】libevent和基于libevent的网络编程
转自: http://www.cnblogs.com/nearmeng/p/4043548.html 1 libevent介绍和安装 介绍 libevent是一个轻量级的基于事件驱动的高性能的开源网络 ...
- libevent和基于libevent的网络编程
1 libevent介绍和安装 介绍 libevent是一个轻量级的基于事件驱动的高性能的开源网络库,并且支持多个平台,对多个平台的I/O复用技术进行了封装,当我们编译库的代码时,编译的脚本将会根据O ...
- Android 从上层到底层-----app层
CPU:RK3288 系统:Android 5.1 功能:上层 app 控制 led 亮灭 开发板:Firefly RK3288 MainActivity.java package com.aaron ...
- Android 从上层到底层-----jni层
CPU:RK3288 系统:Android 5.1 功能:上层 app 控制 led 亮灭 开发板:Firefly RK3288 led_jni.h path:hardware/rockchip/fi ...
- Android 从上层到底层-----hal层
CPU:RK3288 系统:Android 5.1 功能:上层 app 控制 led 亮灭 开发板:Firefly RK3288 led_hal.c path:hardware/rockchip/fi ...
- Android 从上层到底层-----kernel层
CPU:RK3288 系统:Android 5.1 功能:上层 app 控制 led 亮灭 开发板:Firefly RK3288 1.在dts文件中增加 led 设备 path:kernel/arch ...
- Linux、Android系统调用从上层到底层的调用路径浅析
参考: https://blog.csdn.net/liuhangtiant/article/details/85149369 http://blog.sina.com.cn/s/blog_79433 ...
- 第11.13节 Python正则表达式的转义符”\”功能介绍
为了支持特殊元字符在特定场景下能表示自身而不会被当成元字符进行匹配出来,可以通过字符集或转义符表示方法来表示,字符集表示方法前面在<第11.4节 Python正则表达式搜索字符集匹配功能及元字符 ...
- libevent使用<一> libevent导入项目
最近做mysql代理层读写分离,发现在C,C++领域libevent很厉害的样子. 1. 安装libevent linux下源码安装或者直接yum安装. libevent只是一套对一些底层技术的封装, ...
随机推荐
- 2019-8-8-WPF-非客户区的触摸和鼠标点击响应
title author date CreateTime categories WPF 非客户区的触摸和鼠标点击响应 lindexi 2019-08-08 16:48:31 +0800 2019-07 ...
- .NET2.0引用.NET3.5的System.Core.dll&Dapper在.NET2.0下的配置
微软MSDN对.NET2.0,3.0,3.5的描述: .NET Framework 版本 2.0.3.0 和 3.5 是使用同一 CLR 版本 (CLR 2.0) 生成的. 这些版本表示单个安装的连续 ...
- 【python之路43】tornado的用法(一)
一.tonado的代码 1.返回字符串 #!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornad ...
- 【python之路23】递归
1.递归的基础 举例说明:老师要班里坐在最后的一排学生要一本书,老师对前面的人说你向最后一排的同学要一本书,那么最前面的人跟坐在第2排的人说,第2排的人跟第3排的人说,当命令传递到最后一排时,最后一排 ...
- logo 编程
玩了一把logo语言,好学易懂,小朋友有兴趣是个挺不错的玩意.当然也可用于一些机器人等控制 apt install ucblogo ;一个多边形 l 边长 n 边数 to sj :l :n repea ...
- HDU5583 Kingdom of Black and White
Kingdom of Black and White Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Ja ...
- 洛谷P1470 最长前缀
P1470 最长前缀 Longest Prefix 题目描述 在生物学中,一些生物的结构是用包含其要素的大写字母序列来表示的.生物学家对于把长的序列分解成较短的序列(即元素)很感兴趣. 如果一个集合 ...
- PHP配置环境中开启GD库
下配置好的PHP环境中,GD库不像windows那样可以直接用,而是默认关闭,需要把它打开,去到php.ini文件中 找到php_gd2.dll把分号去掉即可.(注:GD库跟绘制二维码等有关)
- web服务发展历程
PhP发展历史1.php: 开始名字含义:personal home page 个人网页 现在名字含义:HyperText Perprocessor 超文本预处理语言 预处理: 说明PHP是在服务器预 ...
- Springboot项目下mybatis报错:Invalid bound statement (not found)
mybatis报错:Invalid bound statement (not found)的原因很多,但是正如报错提示一样,找不到xml中的sql语句,报错的情况分为三种: 第一种:语法错误 Java ...