LiEvent中文帮助文档--第16章【DNS服务器接口】
Libevent
快速可移植非阻塞式网络编程
修订历史
版本
日期
作者
备注
V1.0
2016-11-15
周勇
Libevent编程中文帮助文档
文档是2009-2012年由Nick-Mathewson基于Attribution-Noncommercial-Share Alike许可协议3.0创建,未来版本将会使用约束性更低的许可来创建.
此外,本文档的源代码示例也是基于BSD的"3条款"或"修改"条款.详情请参考BSD文件全部条款.本文档最新下载地址:
英文:http://libevent.org/
中文:http://blog.csdn.net/zhouyongku/article/details/53431750
请下载并运行"gitclonegit://github.com/nmathewson/libevent- book.git"获取本文档描述的最新版本源码.
libevent为实现不重要的 DNS服务器,响应通过 UDP传输的 DNS请求提供了简单机制。本节要求读者对 DNS协议有一定的了解。
16.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);
要开始监听 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版本引入。
16.2检测DNS请求
不幸的是,当前 libevent没有提供较好的获取 DNS请求的编程接口,用户需要包含event2/dns_struct.h文件,查看 evdns_server_request结构体。
未来版本的 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) ,以及请求的主机名。
这些结构体在1.3版本中引入,但是1.4版之前的名字是dns_question_class。名字中的“class”会让C++用户迷惑。仍然使用原来的“class”名字的C 程序将不能在未来发布版本中正确工作。
接口
int evdns_server_request_get_requesting_addr(struct evdns_server_request* req,
struct sockaddr* sa,
int addr_len);
有时 候 需 要 知道 某 特 定 DNS 请 求 来 自 何 方 , 这 时 调 用evdns_server_request_get_requesting_add()就可以了。 应该传入有足够存储空间以容量地址的sockaddr:建议使用sockaddr_storage 结构体。
这个函数在1.3版本中引入.
16.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);
上述函数为请求 req的 DNS 回应的结果节添加一个 RR(类型分别为 A、AAAA和CNAME) 。各个函数中,name是要为之添加结果的主机名,ttl是以秒为单位的存活时间。对于 A和AAAA记录,n是要添加的地址个数,addrs是到原始地址的指针:对于 A记录,是以 n*4字节序列格式给出的IPv4地址; 对于AAAA 记录, 是以n*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的名字字段。type参数是 RR的类型字段 ,其值应该是某个 EVDNS_TYPE_*。dns_class参数是 RR的类别字段。RR的 rdata和rdlength字段将从 data处的 datalen字节中产生。如果 is_name为 true,data将被编码成DNS名字(例如,使用 DNS名字压缩) 。否则,data将被直接包含到 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 回应,带有用户添加的所有RR, 以及错误码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);
如果要为回应消息设置任何标志,可以在发送回应前的任何时候调用这个函数。除了 evdns_server_request_set_flags()首次在2.0.1-alpha版本中出现外,本节描述的所有函数都在1.3版本中引入。
16.4DNS服务器示例
接口
#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;
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;
}