LibEvent中文帮助文档--第14章【使用LibEvent的DNS:高和低层功能】

LiEvent中文帮助文档--第14章

【使用LibEvent的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"获取本文档描述的最新版本源码.

<<上一章>>

14.使用LibEvent的DNS:高和低层功能

LibEvent提供了少量的API来解决DNS名称,以及用于实现简单的DNS服务.

 

我们将由名称查询的高层机制开始介绍,然后介绍低层机制和服务机制.

 

注意LibEvent的当前DNS客户端实现有限制,不支持TCP查询,DNSSec或任意记录类型,我们希望在将来版本LibEvent修复这些问题,但不是当前版本.

 

14.1正文前页:可移植的阻塞式名称解析

为移植已经使用阻塞式名字解析的程序,libevent提供了标准getaddrinfo()接口的可移植实现.对于需要运行在没有getaddrinfo()函数,或者getaddrinfo()不像我们的替代函数那样遵循标准的平台上的程序,这个替代实现很有用.

 

getaddrinfo()接口由RFC 3493的6.1节定义.关于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);

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(默认) ,要么是"任意"(如果设置了EVUTIL_AI_PASSIVE) .

 

hints的ai_flags 字段指示evutil_getaddrinfo如何进行查询,它可以包含0个或者多个以或运算连接的下述标志:

  • EVUTIL_AI_PASSIVE:这个标志指示将地址用于监听,而不是连接.通常二者没有差别,除非nodename为空:对于连接,空的nodename表示localhost (127.0.0.1或者::1) ;而对于监听,空的nodename表示任意(0.0.0.0或者::0) .

  • EVUTIL_AI_CANONNAME:如果设置了这个标志,则函数试图在ai_canonname字段中报告标准名称.

  • EVUTIL_AI_NUMERICHOST:如果设置了这个标志,函数仅仅解析数值类型的IPv4和IPv6地址;如果nodename要求名字查询,函数返回EVUTIL_EAI_NONAME错误.

  • EVUTIL_AI_NUMERICSERV:如果设置了这个标志,函数仅仅解析数值类型的服务名.如果servname不是空,也不是十进制整数,函数返回EVUTIL_EAI_NONAME错误.

  • EVUTIL_AI_V4MAPPED:这个标志表示,如果ai_family是AF_INET6,但是找不到IPv6地址,则应该以v4映射(v4-mapped)型IPv6地址的形式返回结果中的IPv4地址.当前evutil_getaddrinfo()不支持这个标志,除非操作系统支持它.

  • EVUTIL_AI_ALL:如果设置了这个标志和EVUTIL_AI_V4MAPPED,则无论结果是否包含IPv6地址,IPv4地址都应该以v4映射型IPv6地址的形式返回.当前evutil_getaddrinfo()不支持这个标志,除非操作系统支持它.

    EVUTIL_AI_ADDRCONFIG:如果设置了这个标志,则只有系统拥有非本地的IPv4地址时,结果才包含IPv4地址;只有系统拥有非本地的IPv6地址时,结果才包含IPv6地址.

hints的ai_famil y字段指示evutil_getaddrinfo()应该返回哪个地址.字段值可以是AF_INET ,表示只请求IPv4地址;也可以是AF_INET6,表示只请求IPv6地址;或者用AF_UNSPEC表示请求所有可用地址.

 

hints的ai_socktype和ai_protocol字段告知evutil_getaddrinfo()将如何使用返回的地址.这两个字段值的意义与传递给socket()函数的socktype和protocol参数值相同.

 

成功时函数新建一个 evutil_addrinfo结构体链表,存储在*res中,链表的每个元素通过ai_next指针指向下一个元素.因为链表是在堆上分配的,所以需要调用evutil_freeaddrinfo()进行释放.

 

如果失败,函数返回数值型的错误码:

  • EVUTIL_EAI_ADDRFAMILY:请求的地址族对nodename没有意义.

  • EVUTIL_EAI_AGAIN:名字解析中发生可以恢复的错误,请稍后重试.

  • EVUTIL_EAI_FAIL:名字解析中发生不可恢复的错误:解析器或者DNS服务器可能已经崩溃.

  • EVUTIL_EAI_BADFLAGS:hints中的ai_flags字段无效.

  • EVUTIL_EAI_FAMILY:不支持hints中的ai_family字段.

  • EVUTIL_EAI_MEMORY:回应请求的过程耗尽内存.

  • EVUTIL_EAI_NODATA:请求的主机不存在.

  • EVUTIL_EAI_SERVICE:请求的服务不存在.

  • EVUTIL_EAI_SOCKTYPE:不支持请求的套接字类型,或者套接字类型与ai_protocol不匹配.

  • EVUTIL_EAI_SYSTEM:名字解析中发生其他系统错误,更多信息请检查errno.

  • EVUTIL_EAI_CANCEL:应用程序在解析完成前请求取消.evutil_getaddrinfo()函数从不产生这个错误,但是后面描述的evdns_getaddrinfo()可能产生这个错误.调用evutil_gai_strerror()可以将上述错误值转化成描述性的字符串.

注意如果操作系统定义了 addrinfo结构体,则evutil_addrinfo仅仅是操作系统内置的addrinfo结构体的别名.类似地,如果操作系统定义了AI_*标志,则相应的EVUTIL_AI_*标志仅仅是本地标志的别名;如果操作系统定义了EAI_*错误,则相应的EVUTIL_EAI_*只是本地错误码的别名.

 

示例:解析主机名,建立阻塞的连接

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

上述函数和常量是2.0.3-alpha版本新增加的,声明在event2/util.h中.

 

14.2使用 evdns_getaddrinfo()进行非阻塞名字解析

通常的 getaddrinfo(),以及上面的evutil_getaddrinfo()的问题是,它们是阻塞的:调用线程必须等待函数查询DNS服务器,等待回应.对于libevent,这可能不是期望的行为.

 

对于非阻塞式应用,libevent提供了一组函数用于启动DNS请求,让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);

除了不会阻塞在 DNS查询上,而是使用libevent的底层DNS机制进行查询外,evdns_getaddrinfo()和evutil_getaddrinfo()是一样的.因为函数不是总能立即返回结果,所以需要提供一个evdns_getaddrinfo_cb类型的回调函数,以及一个给回调函数的可选的用户参数.

 

此外,调用evdns_getaddrinfo()还要求一个evdns_base指针.evdns_base结构体为libevent的DNS解析器保持状态和配置.关于如何获取evdns_base指针,请看下一节.

 

如果失败或者立即成功,函数返回NULL.否则,函数返回一个evdns_getaddrinfo_request指针.在解析完成之前可以随时使用evdns_getaddrinfo_cancel()和这个指针来取消解析.

 

注意:不论evdns_getaddrinfo()是否返回NULL,是否调用了evdns_getaddrinfo_cancel() ,回调函数总是会被调用.

 

evdns_getaddrinfo()内部会复制nodename、servname和hints参数,所以查询进行过程中不必保持这些参数有效.

 

示例:使用  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 =(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;
}

这些函数是2.0.3-alpha版本新增加的,声明在event2/dns.h中.

 

14.3创建和配置evdns_base

使用 evdns进行非阻塞DNS 查询之前需要配置一个evdns_base.evdns_base存储名字服务器列表和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_base_new()返回一个新建的evdns_base,失败时返回NULL.如果initialize参数为true,函数试图根据操作系统的默认值配置evdns_base;否则,函数让evdns_base为空,不配置名字服务器和选项.

 

可以用 evdns_base_free()释放不再使用的evdns_base.如果fail_request参数为true,函数会在释放evdns_base前让所有进行中的请求使用取消错误码调用其回调函数.

14.3.1使用系统配置初始化evdns

如果需要更多地控制 evdns_base如何初始化,可以为evdns_base_new()的initialize参数传递0,然后调用下述函数.

 

接口

#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文件获取尽量多的信息.

Windows中 没 有 可 以 告 知 名 字 服 务 器 在 哪 里 的resolv.conf文 件,但 可 以 用

evdns_base_config_windows_nameservers()函数从注册表(或者NetworkParams,或者

其他隐藏的地方)读取名字服务器.

 

resolv.conf  文件格式

resolv.conf是一个文本文件,每一行要么是空行,要么包含以#开头的注释,要么由一个跟随零个或者多个参数的标记组成.可以识别的标记有:

  • nameserver:必须后随一个名字服务器的IP地址.作为一个扩展,libevent允许使用IP:Port或者[IPv6]:port语法为名字服务器指定非标准端口.

  • domain:本地域名

  • search:解析本地主机名时要搜索的名字列表.如果不能正确解析任何含有少于"ndots"个点的本地名字,则在这些域名中进行搜索.比如说,如果"search"字段值为example.com,"ndots"为1,则用户请求解析"www"时,函数认为那是"www.example.com".

  • options:空格分隔的选项列表.选项要么是空字符串,要么具有格式option:value(如果有参数) .可识别的选项有:

    • dots:INTEGER:用于配置搜索,请参考上面的"search",默认值是1.

    • timeout:FLOAT:等待DNS服务器响应的时间,单位是秒.默认值为5秒.

    • max-timeouts:INT:名字服务器响应超时几次才认为服务器当机?默认是3次.

    • max-inflight:INT:最多允许多少个未决的DNS请求?(如果试图发出多于这么多个请求,则过多的请求将被延

      迟,直到某个请求被响应或者超时) .默认值是64.

    • attempts:INT:在放弃之前重新传输多少次DNS请求?默认值是3.

    • randomize-case:INT:如果非零,evdns会为发出的DNS请求设置随机的事务ID,并且确认回应具有同样的

      随机事务 ID值.这种称作"0x20 hack"的机制可以在一定程度上阻止对DNS的简单激活事件攻击.这个

      选项的默认值是1.

    •  bind-to:ADDRESS:如果提供,则向名字服务器发送数据之前绑定到给出的地址.对于2.0.4-alpha版本,这

      个设置仅应用于后面的名字服务器条目.

    • initial-probe-timeout:FLOAT:确定名字服务器当机后,libevent以指数级降低的频率探测服务器以判断服务器是否恢复.这个选项配置(探测时间间隔)序列中的第一个超时,单位是秒.默认值是10.

    • getaddrinfo-allow-skew:FLOAT:同时请求IPv4和IPv6地址时, evdns_getaddrinfo()用单独的DNS请求包分
      别请求两种地址 ,因为有些服务器不能在一个包中同时处理两种请求.服务器回应一种地址类型后,函数等待一段时间确定另一种类型的地址是否到达.这个选项配置等待多长时间,单位是秒.默认值是3秒.不识别的字段和选项会被忽略.

14.3.2手动配置evdns

如果需要更精细地控制 evdns的行为,可以使用下述函数:

 

接口

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添加名字服务器 。当前忽略flags参数,为向前兼容考虑,应该传入0。成功时函数返回0,失败时返回负值 。(这个函数在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文件中载入主机文件(格式与/etc/hosts相同) 。成功时函数返回0,失败时返回负值。

 

evdns_base_search_clear()函数从evdns_base中移除所有(通过search配置的)搜索后缀;evdns_base_search_add()则添加后缀。

 

evdns_base_set_option()函数设置evdns_base中某选项的值。选项和值都用字符串表示 。(2.0.3版本之前,选项名后面必须有一个冒号)解析一组配置文件后,可以使用evdns_base_count_nameservers()查看添加了多少个名字服务器。

14.3.3库端配置

有一些为 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的时候,evdns通过这个源来获取难以猜(hard-to-guess)的事务ID以随机化查询(请参考“randomize-case”选项) 。然而,较老版本的libevent没有自己的安全的RNG(随机数发生器) 。此时可以通过调用evdns_set_transaction_id_fn(),传入一个返回难以预测(hard-to-predict)的两字节无符号整数的函数,来为evdns设置一个更好的随机数发生器。

 

2.0.4-alpha以 及 后 续 版 本 中 ,libevent有 自 己 内 置 的 安 全 的RNG,

evdns_set_transaction_id_fn()就没有效果了。

<<下一章>>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值