A Libevent Reference Manual
- Reference Link
- R9: DNS for Libevent
- Using DNS with Libevent: high and low-level functionality
- Low-level DNS interfaces
- DNS server interfaces
- Obsolete DNS interfaces
Reference Link
Fast portable non-blocking network programming with Libevent
R9: DNS for Libevent
Using DNS with Libevent: high and low-level functionality
Libevent 提供了一些用于解析 DNS 名称的 API,以及用于实现简单 DNS 服务器的工具。
我们将首先描述用于名称查找的高级设施,然后描述低级和服务器设施。
Note
Libevent 当前的 DNS 客户端实现存在已知的限制。它不支持 TCP 查找、DNSSec 或任意记录类型。我们想在未来的 Libevent 版本中修复所有这些问题,但目前,它们不存在。
Preliminaries: Portable blocking name resolution
为了帮助移植已经使用阻塞名称解析的程序,Libevent 提供了标准 getaddrinfo()
接口的可移植实现。当您的程序需要在没有 getaddrinfo()
函数或 getaddrinfo()
不符合标准以及我们的替代品的平台上运行时,这会很有帮助。(每个都有惊人的多。)
getaddrinfo()
接口在 RFC 3493 第 6.1 节中指定。请参阅下面的**“Compatibility Notes”**部分,了解我们如何未能实现一致的实现。
Interface
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,失败时返回非零错误代码。
您必须至少提供nodename
和servname
之一。如果 提供了nodename
,则它可以是字面 IPv4
地址(如“127.0.0.1
”)、字面 IPv6
地址(如“::1
”)或 DNS
名称(如“www.example.com
”)。如果提供了servname
,则它是网络服务的符号名称(如“https
”)或包含以十进制给出的端口号的字符串(如“443
”)。
如果未指定servname
,则 *res
中的端口值将设置为零。如果您不指定nodename
,则 *res
中的地址将用于 localhost
(默认情况下)或any
(如果设置了 EVUTIL_AI_PASSIVE
。)
hints
的 ai_flags
字段告诉 evutil_getaddrinfo
如何执行查找。它可以包含零个或多个以下标志,或组合在一起。
-
EVUTIL_AI_PASSIVE
此标志表示我们将使用地址进行侦听,而不是用于连接。通常这没有区别,除非nodename
为 NULL:对于连接,NULL 节点名是localhost
(127.0.0.1 或 ::1),而在侦听时,NULL 节点名是ANY
(0.0.0.0 或 ::0)。 -
EVUTIL_AI_CANONNAME
如果设置了此标志,我们会尝试在ai_canonname
字段中报告主机的规范名称。 -
EVUTIL_AI_NUMERICHOST
设置此标志后,我们仅解析数字IPv4
和IPv6
地址;如果节点名需要名称查找,我们会给出EVUTIL_EAI_NONAME
错误。 -
EVUTIL_AI_NUMERICSERV
设置此标志后,我们仅解析数字服务名称。如果servname
既不是NULL
也不是十进制整数,则给出EVUTIL_EAI_NONAME
错误。 -
EVUTIL_AI_V4MAPPED
此标志表示如果ai_family
是AF_INET6
,并且未找到 IPv6 地址,则结果中的任何 IPv4 地址都应作为 v4 映射的 IPv6 地址返回。evutil_getaddrinfo() 当前不支持它,除非操作系统支持它。 -
EVUTIL_AI_ALL
如果这个标志和EVUTIL_AI_V4MAPPED
都被设置,那么结果中的 IPv4 地址作为4-mapped 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
结构链表,每个链表都用它的“ai_next
”指针指向下一个,并将它们存储在 *res
中。因为这个值是堆分配的,你需要使用 evutil_freeaddrinfo
来释放它。
如果失败,它将返回以下数字错误代码之一:
-
EVUTIL_EAI_ADDRFAMILY
您请求了一个对节点名没有意义的地址族。 -
EVUTIL_EAI_AGAIN
名称解析中存在可恢复的错误;稍后再试。 -
EVUTIL_EAI_FAIL
名称解析中出现不可恢复的错误;您的解析器或 DNS 服务器可能被破坏。 -
EVUTIL_EAI_BADFLAGS
提示中的ai_flags
字段在某种程度上无效。 -
EVUTIL_EAI_FAMILY
提示中的ai_family
字段不是我们支持的字段。 -
EVUTIL_EAI_MEMORY
我们在尝试回答您的请求时内存不足。 -
EVUTIL_EAI_NODATA
您请求的主机存在,但没有与之关联的地址信息。(或者,它没有您请求的类型的地址信息。) -
EVUTIL_EAI_NONAME
您要求的主机似乎不存在。 -
EVUTIL_EAI_SERVICE
您要求的服务似乎不存在。 -
EVUTIL_EAI_SOCKTYPE
我们不支持您要求的套接字类型,或者它与ai_protocol
不兼容。 -
EVUTIL_EAI_SYSTEM
名称解析过程中出现其他一些系统错误。检查errno
以获取更多信息。 -
EVUTIL_EAI_CANCEL
应用程序请求在完成之前取消此 DNS 查找。evutil_getaddrinfo()
函数永远不会产生此错误,但它可能来自evdns_getaddrinfo()
,如下节所述。
您可以使用 evutil_gai_strerror()
将这些结果之一转换为人类可读的字符串。
Note
如果您的操作系统定义了 struct addrinfo
,那么 evutil_addrinfo
只是您操作系统内置结构的别名。同样,如果您的操作系统定义了任何 AI_*
标志,那么相应的 EVUTIL_AI_*
标志只是本机标志的别名;如果您的操作系统定义了任何 EAI_*
错误,则相应的 EVUTIL_EAI_*
代码与您平台的本机错误代码相同。
Example: Resolving a hostname and making a blocking connection
#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 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;
}
这些函数和常量在 Libevent 2.0.3-alpha 中是新的。它们在 event2/util.h
中声明。
Non-blocking hostname resolution with evdns_getaddrinfo()
常规 getaddrinfo()
接口和上面的 evutil_getaddrinfo()
的主要问题是它们正在阻塞:当您调用它们时,您所在的线程必须等待,而它们会查询您的 DNS 服务器并等待为回应。由于您使用的是 Libevent,这可能不是您想要的行为。
所以对于非阻塞使用,Libevent 提供了一组函数来发起 DNS 请求,并使用 Libevent 等待服务器响应。
Interface
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
的指针。这个结构保存了 Libevent 的 DNS 解析器的状态和配置。有关如何获取的更多信息,请参阅下一节。
如果 evdns_getaddrinfo()
函数失败或立即成功,则返回 NULL。否则,它返回一个指向 evdns_getaddrinfo_request
的指针。您可以在请求完成之前的任何时间使用 evdns_getaddrinfo_cancel()
取消请求。
Note
回调函数将最终调用是否evdns_getaddrinfo()
返回null与否,以及是否evdns_getaddrinfo_cancel()
被调用或没有。
当您调用 evdns_getaddrinfo()
时,它会生成其节点名、servname
和提示参数的内部副本:您无需确保在名称查找过程中它们继续存在。
Example: Nonblocking lookups with evdns_getaddrinfo()
#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 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;
}
这些函数在 Libevent 2.0.3-alpha 中是新的。它们在 event2/dns.h
中声明。
Creating and configuring an evdns_base
在使用 evdns
进行非阻塞 DNS 查找之前,您需要配置一个 evdns_base
。每个 evdns_base
存储名称服务器列表和 DNS 配置选项,并跟踪活动和进行中的 DNS 请求。
Interface
struct evdns_base *evdns_base_new(struct event_base *event_base,
int initialize);
void evdns_base_free(struct evdns_base *base, int fail_requests);
evdns_base_new()
函数在成功时返回一个新的 evdns_base
,在失败时返回 NULL。如果initialize
参数为 1,它会尝试根据操作系统的默认设置合理地配置 DNS 基础。如果为 0,则将 evdns_base
留空,不配置名称服务器或选项。
当您不再需要 evdns_base
时,您可以使用 evdns_base_free
将其释放。如果它的fail_requests
参数为真,它将使所有进行中的请求在释放 base
之前使用已取消的错误代码调用它们的回调 。
Initializing evdns from the system configuration
如果您想对 evdns_base
的初始化方式有更多的控制,您可以将 0
作为初始化参数传递给 evdns_base_new
,并调用这些函数之一。
Interface
#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()
函数将扫描存储在filename
中的 resolv.conf
格式文件,并从中读取在flags
中列出的所有选项。(有关 resolv.conf
文件的更多信息,请参阅本地 Unix 手册页。)
DNS_OPTION_SEARCH
告诉 evdns
从 resolv.conf
文件和ndots
选项中读取域和搜索字段,并使用它们来决定哪些域(如果有)来搜索不完全限定的主机名。
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
,或它们隐藏的任何位置)读取所有名称服务器.
The resolv.conf file format
我们识别的 resolv.conf
格式是一个文本文件,其中的每一行都应该是空的,包含一个以 #
字符开头的注释,或者由一个标记后跟零个或多个参数组成。我们识别的tokens是:
nameserver
必须紧跟一个域名服务器的 IP 地址。作为扩展,Libevent 允许您使用 IP:Port
或 [IPv6]:port
语法为名称服务器指定非标准端口。
domain
本地域名。
search
解析本地主机名时要搜索的名称列表。任何包含少于“ndots
”点的名称都被视为本地名称,如果我们无法按原样解析它,我们会查看这些域名。例如,如果“search
”是example.com
,而“ndots
”是1,那么当用户要求我们解析“www
”时,我们会考虑“www.example.com
”。
options
以空格分隔的选项列表。每个选项都作为一个裸字符串给出,或者(如果它需要一个参数)以 option:value
格式给出。公认的选项是:
-
ndots:INTEGER
用于配置搜索。请参阅上面的“搜索”。默认为 1。 -
timeout:FLOAT
在假设我们没有收到 DNS 服务器的响应之前,我们需要等待多长时间(以秒为单位)?默认为 5 秒。 -
max-timeouts:INT
在我们假设它关闭之前,我们允许名称服务器连续超时多少次?默认为 3。 -
max-inflight:INT
我们允许一次挂起多少个 DNS 请求?(如果我们尝试做比这更多的请求,额外的请求将停止,直到较早的请求被回答或超时。)默认为 64。 -
attempts:INT
在放弃 DNS 请求之前,我们要重新传输多少次?默认为 3。 -
randomize-case:INT
如果非零,我们会随机化传出 DNS 请求的大小写,并确保回复与我们的请求具有相同的大小写。这种所谓的“0x20 hack
”可以帮助防止一些针对 DNS 的简单活动事件。默认为 1。 -
bind-to:ADDRESS
如果提供,每当我们将数据包发送到名称服务器时,我们都会绑定到给定的地址。从 Libevent 2.0.4-alpha 开始,它仅适用于后续的名称服务器条目。 -
initial-probe-timeout:FLOAT
当我们确定某个名称服务器出现故障时,我们会以指数下降的频率对其进行探测,以查看它是否已恢复正常。此选项配置系列中的第一个超时,以秒为单位。默认为 10。 -
getaddrinfo-allow-skew:FLOAT
当evdns_getaddrinfo()
同时请求IPv4
地址和IPv6
地址时,它会在单独的 DNS 请求数据包中执行此操作,因为某些服务器无法在一个数据包中处理这两个请求。一旦它得到了一种地址类型的答案,它会等待一段时间,看看是否有另一种类型的答案出现。此选项配置等待多长时间,以秒为单位。默认为 3 秒。
无法识别的标记和选项将被忽略。
Configuring evdns manually
如果您想对 evdns
的行为进行更细粒度的控制,可以使用以下函数:
Interface
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()
函数通过其地址将名称服务器添加到现有 evdns_base
。该标志目前参数将被忽略,并应向前兼容是0。该函数在成功时返回 0,在失败时返回负数。(它是在 Libevent 2.0.7-rc 中添加的。)
evdns_base_nameserver_ip_add
函数将名称服务器添加到现有的 evdns_base
。它采用文本字符串中的名称服务器,可以是 IPv4 地址、IPv6 地址、带有端口的 IPv4 地址 (IPv4:Port)
或带有端口的 IPv6 地址 ([IPv6]:Port)
。成功时返回 0,失败时返回负数。
evdns_base_load_hosts()
函数从hosts_fname
加载一个hosts
文件(格式与/etc/hosts
相同)。它还在成功时返回 0,在失败时返回负数。
evdns_base_search_clear()
函数从 evdns_base
中删除所有当前搜索后缀(由搜索选项配置);evdns_base_search_add()
函数添加了一个后缀。
evdns_base_set_option()
函数将给定选项设置为 evdns_base
中的给定值。每一个都作为一个字符串给出。(在 Libevent 2.0.3 之前,选项名称后面需要有一个冒号。)
如果您刚刚解析了一组配置文件并想查看是否添加了任何名称服务器,您可以使用 evdns_base_count_nameservers()
来查看有多少。
Library-side configuration
您可以使用几个函数来为 evdns
模块指定库范围的设置:
Interface
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
需要一个很好的随机数来源:它使用它来挑选难以猜测的交易 ID,并在使用 0x20 hack
时随机化查询。(请参阅此处的“randomize-case
”选项以获取更多信息。)但是,旧版本的 Libevent 并没有提供自己的安全 RNG
。您可以通过调用 evdns_set_transaction_id_fn
并为其提供一个返回难以预测的两字节无符号整数的函数,从而为 evdns
提供更好的随机数生成器。
在 Libevent 2.0.4-alpha 及更高版本中,Libevent 使用其自己的内置安全 RNG;evdns_set_transaction_id_fn()
无效。
Low-level DNS interfaces
有时,您会希望能够以比从 evdns_getaddrinfo()
获得的更细粒度的控制启动特定的 DNS 请求。Libevent 为您提供了一些接口来做到这一点。
Missing features
现在,Libevent 的 DNS 支持缺少一些您期望从低级 DNS 系统中获得的功能,例如对任意请求类型和 TCP 请求的支持。如果您需要 evdns
没有的功能,请考虑提供补丁。您还可以查看功能更全面的 DNS 库,例如 c-ares
。
Interface
#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
用于请求、一个要查找的资源(用于正向查找的主机名或用于反向查找的地址)、一组用于确定如何进行查找的标志、一个在查找时调用的回调完成,并传递给用户提供的回调的指针。
该标志参数为0或DNS_QUERY_NO_SEARCH
在搜索列表中,如果原来的搜索失败明确禁止搜索。DNS_QUERY_NO_SEARCH
对反向查找没有影响,因为它们从不进行搜索。
当请求完成时——无论成功与否——回调函数将被调用。回调需要一个结果,表示成功或错误代码(见下面的DNS错误表),记录类型(DNS_IPv4_A
,DNS_IPv6_AAAA
,或DNS_PTR
之一)的记录数的地址,时间到以秒为单位,地址本身,以及用户提供的参数指针。
发生错误时,回调的addresses
参数为NULL。对于 PTR
记录,它是一个以 NUL 结尾的字符串。对于 IPv4
记录,它是一个按网络顺序排列的四字节值数组。对于 IPv6
记录,它是一个按网络顺序排列的 16 字节记录数组。(请注意,即使没有错误,地址数也可以为 0。当名称存在但没有请求类型的记录时,可能会发生这种情况。)
可以传递给回调的错误代码如下:
DNS Errors
Code | Meaning |
---|---|
DNS_ERR_NONE | No error occurred |
DNS_ERR_FORMAT | The server didn’t understand the query |
DNS_ERR_SERVERFAILED | The server reported an internal error |
DNS_ERR_NOTEXIST | There was no record with the given name |
DNS_ERR_NOTIMPL | The server doesn’t understand this kind of query |
DNS_ERR_REFUSED | The server rejected the query for policy reasons |
DNS_ERR_TRUNCATED | The DNS record wouldn’t fit in a UDP packet |
DNS_ERR_UNKNOWN | Unknown internal error |
DNS_ERR_TIMEOUT | We waited too long for an answer |
DNS_ERR_SHUTDOWN | The user asked us to shut down the evdns system |
DNS_ERR_CANCEL | The user asked us to cancel this request |
DNS_ERR_NODATA | The response arrived, but contained no answers |
(DNS_ERR_NODATA 是 2.0.15 稳定版中的新内容。)
您可以使用以下命令将这些错误代码解码为人类可读的字符串:
Interface
const char *evdns_err_to_string(int err);
每个解析函数都返回一个指向不透明evdns_request
结构的指针。您可以在调用回调之前的任何时候使用它来取消请求:
Interface
void evdns_cancel_request(struct evdns_base *base,
struct evdns_request *req);
使用此函数取消请求会使用 DNS_ERR_CANCEL
结果代码调用其回调。
Suspending DNS client operations and changing nameservers
有时您希望重新配置或关闭 DNS 子系统,而不会过多地影响正在进行的 DNS 请求。
Interface
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_base_resume()
。
这些函数在成功时返回 0,在失败时返回 -1。它们是在 Libevent 2.0.1-alpha 中引入的。
DNS server interfaces
Libevent 提供了简单的功能来充当普通的 DNS 服务器并响应 UDP DNS 请求。
本节假设您对 DNS 协议有一定的了解。
Creating and closing a DNS server
Interface
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);
要开始侦听 DNS 请求,请调用 evdns_add_server_port_with_base()
。它需要一个 event_base
用于事件处理;要监听的 UDP 套接字;一个标志变量(现在总是 0);收到新的 DNS 查询时调用的回调函数;和一个指向将传递给回调的用户数据的指针。它返回一个新的 evdns_server_port
对象。
完成 DNS 服务器后,您可以将其传递给 evdns_close_server_port()
。
evdns_add_server_port_with_base()
函数是 2.0.1-alpha 中的新函数;evdns_close_server_port()
是在 1.3 中引入的。
Examining a DNS request
不幸的是,Libevent 目前没有提供一种通过编程接口查看 DNS 请求的好方法。相反,您被困在包含 event2/dns_struct.h
并手动查看 evdns_server_request
结构中。
如果 Libevent 的未来版本提供了一种更好的方法来做到这一点,那就太好了。
Interface
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
是一个指向struct evdns_server_question
的指针数组。每个 evdns_server_question
包括请求的资源类型(请参阅下面的 EVDNS_*_TYPE
宏列表)、请求的类(通常为 EVDNS_CLASS_INET
)和请求的主机名。
这些结构是在 Libevent 1.3 中引入的。在 Libevent 1.4 之前, dns_question_class
被称为“类”,这给 C++ 人带来了麻烦。仍使用旧“类”名称的 C 程序将在未来版本中停止工作。
Interface
int evdns_server_request_get_requesting_addr(struct evdns_server_request *req,
struct sockaddr *sa, int addr_len);
有时您会想知道哪个地址发出了特定的 DNS 请求。您可以通过调用 evdns_server_request_get_requesting_addr()
来检查这一点。您应该传入一个具有足够存储空间来保存地址的 sockaddr
:建议使用 struct sockaddr_storage
。
这个函数是在 Libevent 1.3c 中引入的。
Responding to DNS requests
每次您的 DNS 服务器收到请求时,该请求都会与您的 user_data
指针一起传递给您提供的回调函数。回调函数必须要么响应请求,要么忽略请求,要么确保请求最终得到响应或忽略。
在响应请求之前,您可以在响应中添加一个或多个答案:
Interface
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);
上面的函数都会向请求req
的 DNS 回复的答案部分添加一个 RR
(分别为 A
、AAAA
或 CNAME
类型)。在每种情况下,参数名称都是要为其添加答案的主机名,而 ttl
是以秒为单位的答案的生存时间值。对于 A
和 AAAA
记录,n
是要添加的地址数,addrs
是指向原始地址的指针,可以作为 A
记录中 IPv4 地址的 n*4
字节序列给出,或者作为 n* 序列给出AAAA 记录中 IPv6 地址的 16 个字节。
这些函数在成功时返回 0,在失败时返回 -1。
Interface
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 域中的地址)**中的一个,以指示您要为其提供响应的地址。该主机名 的说法是针对PTR查找答案。
Interface
#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);
此函数将任意 RR
添加到请求req
的 DNS 回复中。该部分参数描述这部分将它添加到,而且应该是EVDNS _ * _ SECTION
值之一。该名称参数是RR
的名称字段。该类型参数是类型的RR的领域,应该是EVDNS_TYPE_ *
值如果可能的话之一。该dns_class
参数是RR的类领域,一般应EVDNS_CLASS_INET
。该TTL
参数是在RR秒的时间生存。RR 的 rdata 和 rdlength
字段将从data 中提供的datalen
字节 生成. 如果 is_name
为真,数据将被编码为 DNS 名称(即,使用 DNS 名称压缩)。否则,将逐字包括在内。
Interface
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 响应,包括您附加到它的所有 RR
,错误代码为err
。如果您收到一个不想响应的请求,您可以通过调用 evdns_server_request_drop()
来释放所有关联的内存和簿记结构来忽略它。
Interface
#define EVDNS_FLAGS_AA 0x400
#define EVDNS_FLAGS_RD 0x080
void evdns_server_request_set_flags(struct evdns_server_request *req,
int flags);
如果要在响应消息上设置任何标志,可以在发送响应之前随时调用此函数。
本节中的所有函数都是在 Libevent 1.3 中引入的,除了 evdns_server_request_set_flags()
首次出现在 Libevent 2.0.1-alpha 中。
DNS Server example
Example: A trivial DNS responder
#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 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;
}
Obsolete DNS interfaces
Obsolete Interfaces
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 地址的名称服务器。特殊地,它将它们作为网络顺序中的四个字节。
在 Libevent 2.0.1-alpha 之前,无法为 DNS 服务器端口指定事件库。您必须改用 evdns_add_server_port()
,它采用默认的 event_base
。
从 Libevent 2.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
。
在 Libevent 2.0.1-alpha 之前,没有 evdns_base
的单独概念:evdns
子系统中的所有信息都全局存储,并且操作它的函数不将 evdns_base
作为参数。它们现在都已弃用,仅在 event2/dns_compat.h
中声明。它们是通过单个全局 evdns_base
实现的;您可以通过调用 Libevent 2.0.3-alpha 中引入的 evdns_get_global_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
宏。