Book: Programming with Libevent(2)--A Libevent Reference Manual(4)

Libevent提供了非阻塞的DNS解析和简单的DNS服务器功能。非阻塞解析通过evdns_getaddrinfo()实现,允许在等待响应时继续处理事件。DNS服务器接口允许创建DNS服务器来处理UDP请求,支持添加自定义响应。此外,Libevent还提供了对resolv.conf文件的解析以配置DNS设置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 中给出的规则解析提供的 nodenameservname 字段,并为您构建一个 evutil_addrinfo 结构的链表并将它们存储在 *res 中。成功时返回 0,失败时返回非零错误代码。

您必须至少提供nodenameservname 之一。如果 提供了nodename,则它可以是字面 IPv4 地址(如“127.0.0.1”)、字面 IPv6 地址(如“::1”)或 DNS 名称(如“www.example.com”)。如果提供了servname,则它是网络服务的符号名称(如“https”)或包含以十进制给出的端口号的字符串(如“443”)。

如果未指定servname,则 *res 中的端口值将设置为零。如果您不指定nodename,则 *res 中的地址将用于 localhost(默认情况下)或any(如果设置了 EVUTIL_AI_PASSIVE。)

hintsai_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
    设置此标志后,我们仅解析数字 IPv4IPv6 地址;如果节点名需要名称查找,我们会给出 EVUTIL_EAI_NONAME 错误。

  • EVUTIL_AI_NUMERICSERV
    设置此标志后,我们仅解析数字服务名称。如果servname既不是 NULL 也不是十进制整数,则给出 EVUTIL_EAI_NONAME 错误。

  • EVUTIL_AI_V4MAPPED
    此标志表示如果 ai_familyAF_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 地址时才包含在结果中。

hintsai_family 字段用于告诉 evutil_getaddrinfo() 它应该返回哪个地址。可以是 AF_INET 仅请求 IPv4 地址,AF_INET6 仅请求 IPv6 地址,或 AF_UNSPEC 请求所有可用地址。

hintsai_socktypeai_protocol 字段用于告诉 evutil_getaddrinfo() 您将如何使用该地址。它们与传递给 socket()socktypeprotocol 字段相同。

如果 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
告诉 evdnsresolv.conf 文件和ndots选项中读取域和搜索字段,并使用它们来决定哪些域(如果有)来搜索不完全限定的主机名。

DNS_OPTION_NAMESERVERS
该标志告诉 evdnsresolv.conf 文件中了解名称服务器

DNS_OPTION_MISC
告诉 evdnsresolv.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_ADNS_IPv6_AAAA,或DNS_PTR之一)的记录数的地址,时间到以秒为单位,地址本身,以及用户提供的参数指针。

发生错误时,回调的addresses参数为NULL。对于 PTR 记录,它是一个以 NUL 结尾的字符串。对于 IPv4 记录,它是一个按网络顺序排列的四字节值数组。对于 IPv6 记录,它是一个按网络顺序排列的 16 字节记录数组。(请注意,即使没有错误,地址数也可以为 0。当名称存在但没有请求类型的记录时,可能会发生这种情况。)

可以传递给回调的错误代码如下:

DNS Errors

CodeMeaning
DNS_ERR_NONENo error occurred
DNS_ERR_FORMATThe server didn’t understand the query
DNS_ERR_SERVERFAILEDThe server reported an internal error
DNS_ERR_NOTEXISTThere was no record with the given name
DNS_ERR_NOTIMPLThe server doesn’t understand this kind of query
DNS_ERR_REFUSEDThe server rejected the query for policy reasons
DNS_ERR_TRUNCATEDThe DNS record wouldn’t fit in a UDP packet
DNS_ERR_UNKNOWNUnknown internal error
DNS_ERR_TIMEOUTWe waited too long for an answer
DNS_ERR_SHUTDOWNThe user asked us to shut down the evdns system
DNS_ERR_CANCELThe user asked us to cancel this request
DNS_ERR_NODATAThe 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);

上面的函数都会向请求reqDNS 回复的答案部分添加一个 RR(分别为 AAAAACNAME 类型)。在每种情况下,参数名称都是要为其添加答案的主机名,而 ttl是以秒为单位的答案的生存时间值。对于 AAAAA 记录,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 记录添加到请求的应答部分。参数reqttl如上所述。您必须提供**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 添加到请求reqDNS 回复中。该部分参数描述这部分将它添加到,而且应该是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 functionObsolete 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 宏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值