原文: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_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_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;
}