libevent學習筆記【使用篇】——9. Libevent和DNS解析

原文:blog.csdn.com/windeal3203
譯自:http://www.wangafu.net/~nickm/libevent-book/Ref9_dns.html

Libevent 提供了一些用於解析DNS域名的API, 以及一些用於實現DNS Server的接口。

可移植的阻塞型域名解析

Libevent 提供了標準庫函數getaddrinfo的可移植版本,用於讓阻塞性域名解析應用於可移植程序。(畢竟有些平臺並不支持getaddrinfo函數,或者該函數相對於libevent的替代接口表現更差)。

struct evutil_addrinfo {
    int ai_flags;
    int ai_family;
    int ai_socktype;
    int ai_protocol;
    size_t ai_addrlen;
    char *ai_canonname;
    struct sockaddr *ai_addr;
    struct evutil_addrinfo *ai_next;
};

#define EVUTIL_AI_PASSIVE     /* ... */
#define EVUTIL_AI_CANONNAME   /* ... */
#define EVUTIL_AI_NUMERICHOST /* ... */
#define EVUTIL_AI_NUMERICSERV /* ... */
#define EVUTIL_AI_V4MAPPED    /* ... */
#define EVUTIL_AI_ALL         /* ... */
#define EVUTIL_AI_ADDRCONFIG  /* ... */

int evutil_getaddrinfo(const char *nodename, const char *servname,
    const struct evutil_addrinfo *hints, struct evutil_addrinfo **res);
void evutil_freeaddrinfo(struct evutil_addrinfo *ai);
const char *evutil_gai_strerror(int err);
  • notename: 節點名稱,如IP地址(127.0.0.1, ::1) 或域名www.example.com 等,如果值爲控偶,則表示localhost(默認情況下),或者·any·(EVUTIL_AI_PASSIVE)被設置的情況下
  • servname: 服務名,如https或者端口號。如果沒有指定servname,則*res中的port會被設置爲0.
  • hints: 參數提供了與解析規則相關的參數。
  • res: 返回的解析結果列表。
    notename和servname至少要指定一個

在解析規則hints中,包含了成員ai_flag,它指定了如何進行lookup(查詢)。下面是一些常用的值:

  • EVUTIL_AI_PASSIVE: 指明我們將用這個地址來進行listening 而不是connecting。(一般當 nodename爲NULL時纔有用。當nodename爲NULL時, NULL nodename在connecting時表示localhost;在listening時表示ANY)
  • EVUTIL_AI_CANONNAME: 該標誌位設置了表示:我們將在ai_canonname字段中顯示canonical名字。
  • EVUTIL_AI_NUMERICHOST: 表示只解析(數字形式)IPv4或者IPv6地址。 當nodename被指定爲名字解析時,將返回EVUTIL_EAI_NONAME錯誤。
  • EVUTIL_AI_V4MAPPED: 如果ai_family字段被指定爲AF_INET6, 而又解析不到IPv6的地址,那麼這個解析到的IPv4的地址將作爲IPv6地址的映射返回。(取決於系統是否支持。)
  • EVUTIL_AI_ALL: 如果這個flag以及EVUTIL_AI_V4MAPPED都被設置了,那麼不管有沒有解析到IPv6的地址, 在解析結果中的IPv4地址都會作爲4-mapped IPv6地址形式被保存。

如果evutil_getaddrinfo解析成功了,則它會分配一個節點爲evutil_addrinfo structures類型的鏈表res,用於保存每一個解析到的地址。這些內存是從堆中分配的,可以通過evutil_freeaddrinfo釋放內存。
如果evutil_getaddrinfo解析失敗了,則它會返回一個錯誤碼(如EVUTIL_EAI_ADDRFAMILY)可以通過evutil_gai_strerror解析。

// Example

#include <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;
}

evdns_getaddrinfo: 非阻塞的域名解析

不管是標準的getaddrinfo還是我們前面介紹的evutil_getaddrinfo都存在一個問題:它們都是阻塞的。
然而,我們一般講Libevent應用於非阻塞環境中,因而最好有一個非阻塞的域名解析接口。
慶幸的是,Libevent確實提供了非阻塞的域名解析接口。

typedef void (*evdns_getaddrinfo_cb)(
    int result, struct evutil_addrinfo *res, void *arg);
struct evdns_getaddrinfo_request;

struct evdns_getaddrinfo_request *evdns_getaddrinfo(
    struct evdns_base *dns_base,
    const char *nodename, const char *servname,
    const struct evutil_addrinfo *hints_in,
    evdns_getaddrinfo_cb cb, void *arg);

void evdns_getaddrinfo_cancel(struct evdns_getaddrinfo_request *req);

從函數原型中, 不難看出,evdns_getaddrinfo的使用方法與其他使用回調函數實現非阻塞的函數類似的。其參數的作用可以參考阻塞型的evutil_getaddrinfo
evdns_getaddrinfo有三種返回值:

  • NULL: 調用失敗,或者立即得到了結果。
  • 返回指向evdns_getaddrinfo_request類型的指針: 可以通過該指針取消DNS解析請求。
//Example:

#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;
}

創建和配置evdns_base

在使用evdns_getaddrinfo之前,我們需要先配置一個evdns_base用於保存nameservers列表、DNS 配置選項、追蹤活躍的和準備發射的DNS請求。

struct evdns_base *evdns_base_new(struct event_base *event_base,
       int initialize);
void evdns_base_free(struct evdns_base *base, int fail_requests);

從系統配置(文件)初始化evdns

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

#define DNS_OPTION_SEARCH 1
#define DNS_OPTION_NAMESERVERS 2
#define DNS_OPTION_MISC 4
#define DNS_OPTION_HOSTSFILE 8
#define DNS_OPTIONS_ALL 15
int evdns_base_resolv_conf_parse(struct evdns_base *base, int flags,
                                 const char *filename);

#ifdef WIN32
int evdns_base_config_windows_nameservers(struct evdns_base *);
#define EVDNS_BASE_CONFIG_WINDOWS_NAMESERVERS_IMPLEMENTED
#endif

evdns_base_resolv_conf_parse()函數掃描resolv.conf格式的文件filename,從中讀取flags指示的選項(關於resolv.conf文件的更多信息,請看Unix手冊)。

  • DNS_OPTION_SEARCH: 請求從resolv.conf文件讀取domain和search字段以及ndots選項,使用它們來確定使用哪個域(如果存在)來搜索不是全限定的主機名。
  • DNS_OPTION_NAMESERVERS: 請求從resolv.conf中讀取名字服務器地址。
  • DNS_OPTION_MISC: 請求從resolv.conf文件中讀取其他配置選項。
  • DNS_OPTION_HOSTSFILE: 請求從/etc/hosts文件讀取主機列表。
  • DNS_OPTION_ALL: 請求從resolv.conf文件獲取儘量多的信息。

手動配置 evdns

Libevent 同樣也提供了手動配置evdns的方法,以便更加靈活地運用dvdns

int evdns_base_nameserver_sockaddr_add(struct evdns_base *base,
                                 const struct sockaddr *sa, ev_socklen_t len,
                                 unsigned flags);
int evdns_base_nameserver_ip_add(struct evdns_base *base,
                                 const char *ip_as_string);
int evdns_base_load_hosts(struct evdns_base *base, const char *hosts_fname);

void evdns_base_search_clear(struct evdns_base *base);
void evdns_base_search_add(struct evdns_base *base, const char *domain);
void evdns_base_search_ndots_set(struct evdns_base *base, int ndots);

int evdns_base_set_option(struct evdns_base *base, const char *option,
    const char *val);

int evdns_base_count_nameservers(struct evdns_base *base);
  • evdns_base_nameserver_sockaddr_add函數:通過socket地址的形式,向evdns_base添加一個域名服務器。flags參數目前是被忽略的,而且爲了向前兼容性應該置爲0。該函數成功是返回0,失敗時返回負數。
  • evdns_base_nameserver_ip_add函數:也是向evdns_base添加域名服務器。不過它是以字符串的形式添加,該字符串可以是一個IPv4地址,一個IPv6地址,一個有端口號的IPv4地址(IPv4:Port),或者一個有端口號的IPv6地址([IPv6]:port)。該函數成功時返回0,失敗時返回負數。
  • evdns_base_load_hosts函數:從hosts_name中加載一個主機文件(類似於/etc/hosts的格式)。該函數成功時返回0,失敗時返回負數。
  • evdns_base_search_clear函數:從evdns_base中清除所有當前的search後綴(就像search選項中配置的那樣);
  • evdns_base_search_add函數:則增加一個後綴。
  • evdns_base_set_option函數:向evdns_base添加一個選項key和該選項的值value,key和value都是字符串的形式。(2.0.3版本之前,選項名後面必須有一個冒號)
  • evdns_base_count_nameservers函數:解析一系列的配置文件之後,如果希望看到是否已經添加了域名服務器,可以使用這個函數來查看有多少個域名服務器。

庫端的配置

Libevent提供了一對函數可以對evdns模塊進行庫級別的設置:

typedef  void (*evdns_debug_log_fn_type)(int  is_warning,  const  char  *msg);
void  evdns_set_log_fn(evdns_debug_log_fn_type  fn);
void  evdns_set_transaction_id_fn(ev_uint16_t  (*fn)(void));

由於歷史的原因,evdns子系統具有其自己獨立的日誌功能;可以使用evdns_set_log_fn函數添加回調函數,對消息進行處理。
出於安全性的考慮,evdns需要一個好的隨機數源:在使用0x20 hack時,它被用來獲取難以被猜中的事務ID,從而用來隨機化查詢(參考randomize-case選項)。然而老版本的Libevent,並沒有提供一個安全的RNG。可以通過調用evdns_set_transaction_id_fn ,並向其提供一個能夠返回難以預測的2字節無符號整數的函數,來爲evdns設置一個更好的隨機數產生器
在Libevent2.0.4-alpha及其之後的版本,Libevent使用自己的內部的安全RNG;所以evdns_set_transaction_id_fn函數不在起作用。

evdns 底層接口

Libevent提供了更加底層的接口,以便用來控制進行特別的DNS請求。

#define DNS_QUERY_NO_SEARCH /* ... */

#define DNS_IPv4_A         /* ... */
#define DNS_PTR            /* ... */
#define DNS_IPv6_AAAA      /* ... */

typedef void (*evdns_callback_type)(int result, char type, int count,
    int ttl, void *addresses, void *arg);

struct evdns_request *evdns_base_resolve_ipv4(struct evdns_base *base,
    const char *name, int flags, evdns_callback_type callback, void *ptr);
struct evdns_request *evdns_base_resolve_ipv6(struct evdns_base *base,
    const char *name, int flags, evdns_callback_type callback, void *ptr);
struct evdns_request *evdns_base_resolve_reverse(struct evdns_base *base,
    const struct in_addr *in, int flags, evdns_callback_type callback,
    void *ptr);
struct evdns_request *evdns_base_resolve_reverse_ipv6(
    struct evdns_base *base, const struct in6_addr *in, int flags,
    evdns_callback_type callback, void *ptr);

這些解析函數爲一個特殊的記錄發起DNS請求。每一個函數都使用·evdns_base·來進行請求,函數參數還包括需要查詢的源(既可以是正向查找時的主機名,也可以是反向查找時的一個地址),一系列決定如何進行查找的標誌集合,當查找結束時調用的回調函數,以及一個用戶提供的傳遞給回調函數的參數的指針。
flags參數可以是0,或者是DNS_QUERY_NO_SEARCHDNS_QUERY_NO_SEARCH表示在原始搜索失敗的時候,明確禁止在search列表中進行搜索。DNS_QUERY_NO_SEARCH 標誌對於反向查詢沒有意義,因爲反向查詢不會進行搜索。
當請求完成時(成功或失敗),就會調用回調函數。回調函數的參數有:result表明成功或者是一個錯誤碼(參看下面的DNS錯誤碼),一個記錄類型(DNS_IPv4_A,DNS_IPv6_AAAA或 DNS_PTR之一),addresses是記錄的個數,ttl秒數,地址本身以及用戶提供的參數指針。
如果發生了錯誤,則回調函數的addresses參數爲NULL。如果沒有發生錯誤,對於PTR記錄來說,它是一個NULL結尾的字符串。對於IPv4記錄來說,它是一個網絡字節序的4字節的數組。對於IPv6來說,則是一個網絡字節序的16字節數組。(注意,即使沒有錯誤,addresses的數量也可以是0。比如名字存在,但是卻沒有請求類型的記錄)

可以傳遞的回調函數的錯誤碼如下:

  • DNS_ERR_NONE: 沒有錯誤發生
  • DNS_ERR_FORMAT: 服務器無法理解該請求
  • DNS_ERR_SERVERFAILED: 服務器發生了內部錯誤
  • DNS_ERR_NOTEXIT: 對於給定的name,美譽record
  • DNS_ERR_NOTIMPL: 服務器無法理解該類型的請求
  • DNS_ERR_REFUSED: 因策略設置,服務器決絕該請求
  • DNS_ERR_UNKNOWN: 未知的內部錯誤
  • DNS_ERR_TIMEOUT:請求超時
  • DNS_ERR_SHUTDOWN: 用戶要求關閉evdns系統
  • DNS_ERR_CANCEL: 用戶要求取消這次請求
  • DNS_ERR_NODATA: 雖然受到了響應,但是其中卻沒有包含答案。

可以使用下面的接口將錯誤碼轉換爲可讀的字符串:

const  char  *evdns_err_to_string(int err);

每一個解析函數都返回一個不透明的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(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);

該函數向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 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;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章