Libevent参考手册:使用libevent的DNS:高层和底层功能 (2011-08-11 21:13:16)

Libevent参考手册:使用libevent的DNS:高层和底层功能

  (2011-08-11 21:13:16)
标签: 

dns

 

it

 

libevent

 

网络编程

分类: Libevent

译自http://www.wangafu.net/~nickm/libevent-book/Ref9_dns.html

libevent提供了少量用于解析DNS名字的API,以及用于实现简单DNS服务器的机制。

我们从用于名字查询的高层机制开始介绍,然后介绍底层机制和服务器机制。

注意

libevent当前的DNS客户端实现存在限制:不支持TCP查询、DNSSec以及任意记录类型。未来版本的libevent会修正这些限制。

预备:可移植的阻塞式名字解析

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

getaddrinfo()接口由RFC 3493的6.1节定义。关于libevent如何不满足其一致性实现的概述,请看下面的“兼容性提示”节。

接口

Libevent参考手册:使用libevent的DNS:高层和底层功能

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_family字段指示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_*只是本地错误码的别名。

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

Libevent参考手册:使用libevent的DNS:高层和底层功能

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

使用evdns_getaddrinfo()的非阻塞式名字解析

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

对于非阻塞式应用,libevent提供了一组函数用于启动DNS请求,让libevent等待服务器回应。

接口

Libevent参考手册:使用libevent的DNS:高层和底层功能

除了不会阻塞在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()的非阻塞查询

Libevent参考手册:使用libevent的DNS:高层和底层功能

 

Libevent参考手册:使用libevent的DNS:高层和底层功能

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

创建和配置evdns_base

使用evdns进行非阻塞DNS查询之前需要配置一个evdns_base。evdns_base存储名字服务器列表和DNS配置选项,跟踪活动的、进行中的DNS请求。

接口

Libevent参考手册:使用libevent的DNS:高层和底层功能

成功时evdns_base_new()返回一个新建的evdns_base,失败时返回NULL。如果initialize参数为true,函数试图根据操作系统的默认值配置evdns_base;否则,函数让evdns_base为空,不配置名字服务器和选项。

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

使用系统配置初始化evdns

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

接口

Libevent参考手册:使用libevent的DNS:高层和底层功能

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(如果有参数)。可识别的选项有:

ndots:INTEGER

用于配置搜索,请参考上面的“search”,默认值是1。

timeout:FLOAT

等待DNS服务器响应的时间,单位是秒。默认值为5秒。

max-timeouts:INT

名字服务器响应超时几次才认为服务器当机?默认是3次。

max-inflight:INT

最多允许多少个未决的DNS请求?(如果试图发出多于这么多个请求,则过多的请求将被延迟,直到某个请求被响应或者超时)。默认值是XXX。

attempts:INT

在放弃之前重新传输多少次DNS请求?默认值是XXX。

randomize-case:INT

如果非零,evdns会为发出的DNS请求设置随机的事务ID,并且确认回应具有同样的随机事务ID值。这种称作“0x20 hack”的机制可以在一定程度上阻止对DNS的简单激活事件攻击。这个选项的默认值是1。

(这段原文不易理解,译文可能很不准确。这里给出原文:If nonzero,we randomize the case on outgoing DNS requests and make sure that replies have the same case as our requests.This so-called "0x20 hack" can help prevent some otherwise simple active events against DNS.)

bind-to:ADDRESS

如果提供,则向名字服务器发送数据之前绑定到给出的地址。对于2.0.4-alpha版本,这个设置仅应用于后面的名字服务器条目。

initial-probe-timeout:FLOAT

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

getaddrinfo-allow-skew:FLOAT

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

不识别的字段和选项会被忽略。

手动配置evdns

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

接口

Libevent参考手册:使用libevent的DNS:高层和底层功能

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()查看添加了多少个名字服务器。

库端配置

有一些为evdns模块设置库级别配置的函数:

接口

Libevent参考手册:使用libevent的DNS:高层和底层功能

因为历史原因,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()就没有效果了。

底层DNS接口

有时候需要启动能够比从evdns_getaddrinfo()获取的DNS请求进行更精细控制的特别的DNS请求,libevent也为此提供了接口。

缺少的特征

当前libevent的DNS支持缺少其他底层DNS系统所具有的一些特征,如支持任意请求类型和TCP请求。如果需要evdns所不具有的特征,欢迎贡献一个补丁。也可以看看其他全特征的DNS库,如c-ares。

接口

Libevent参考手册:使用libevent的DNS:高层和底层功能

这些解析函数为一个特别的记录发起DNS请求。每个函数要求一个evdns_base用于发起请求、一个要查询的资源(正向查询时的主机名,或者反向查询时的地址)、一组用以确定如何进行查询的标志、一个查询完成时调用的回调函数,以及一个用户提供的传给回调函数的指针。

flags参数可以是0,也可以用DNS_QUERY_NO_SEARCH明确禁止原始查询失败时在搜索列表中进行搜索。DNS_QUERY_NO_SEARCH对反向查询无效,因为反向查询不进行搜索。

请求完成(不论是否成功)时回调函数会被调用。回调函数的参数是指示成功或者错误码(参看下面的DNS错误表)的result、一个记录类型(DNS_IPv4_A、DNS_IPv6_AAAA,或者DNS_PTR)、addresses中的记录数、以秒为单位的存活时间、地址(查询结果),以及用户提供的指针。

发生错误时传给回调函数的addresses参数为NULL。没有错误时:对于PTR记录,addresses是空字符结束的字符串;对于IPv4记录,则是网络字节序的四字节地址值数组;对于IPv6记录,则是网络字节序的16字节记录数组。(注意:即使没有错误,addresses的个数也可能是0。名字存在,但是没有请求类型的记录时就会出现这种情况)

可能传递给回调函数的错误码如下:

DNS错误码

错误码

意义

DNS_ERR_NONE

没有错误

DNS_ERR_FORMAT

服务器不识别查询请求

DNS_ERR_SERVERFAILED

服务器内部错误

DNS_ERR_NOTEXIST

没有给定名字的记录

DNS_ERR_NOTIMPL

服务器不识别这种类型的查询

DNS_ERR_REFUSED

因为策略设置,服务器拒绝查询

DNS_ERR_TRUNCATED

DNS记录不适合UDP分组

DNS_ERR_UNKNOWN

未知的内部错误

DNS_ERR_TIMEOUT

等待超时

DNS_ERR_SHUTDOWN

用户请求关闭evdns系统

DNS_ERR_CANCEL

用户请求取消查询

 

可以用下述函数将错误码转换成错误描述字符串:

接口

Libevent参考手册:使用libevent的DNS:高层和底层功能

每个解析函数都返回不透明的evdns_request结构体指针。回调函数被调用前的任何时候都可以用这个指针来取消请求:

接口

Libevent参考手册:使用libevent的DNS:高层和底层功能

用这个函数取消请求将使得回调函数被调用,带有错误码DNS_ERR_CANCEL。

挂起DNS客户端操作,更换名字服务器

有时候需要重新配置或者关闭DNS子系统,但不能影响进行中的DNS请求。

接口

Libevent参考手册:使用libevent的DNS:高层和底层功能

evdns_base_clear_nameservers_and_suspend()会移除所有名字服务器,但未决的请求会被保留,直到随后重新添加名字服务器,调用evdns_base_resume()。

这些函数成功时返回0,失败时返回-1。它们在2.0.1-alpha版本引入。

DNS服务器接口

libevent为实现不重要的DNS服务器,响应通过UDP传输的DNS请求提供了简单机制。本节要求读者对DNS协议有一定的了解。

创建和关闭DNS服务器

接口

Libevent参考手册:使用libevent的DNS:高层和底层功能

 

要开始监听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版本引入。

检测DNS请求

不幸的是,当前libevent没有提供较好的获取DNS请求的编程接口,用户需要包含event2/dns_struct.h文件,查看evdns_server_request结构体。

未来版本的libevent应该会提供更好的方法。

接口

Libevent参考手册:使用libevent的DNS:高层和底层功能

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程序将不能在未来发布版本中正确工作。

接口

Libevent参考手册:使用libevent的DNS:高层和底层功能

有时候需要知道某特定DNS请求来自何方,这时调用evdns_server_request_get_requesting_add()就可以了。应该传入有足够存储空间以容量地址的sockaddr:建议使用sockaddr_storage结构体。

这个函数在1.3版本中引入。

响应DNS请求

DNS服务器收到每个请求后,会将请求传递给用户提供的回调函数,还带有用户数据指针。回调函数必须响应请求或者忽略请求,或者确保请求最终会被回答或者忽略。

回应请求前可以向回应中添加一个或者多个答案:

接口

Libevent参考手册:使用libevent的DNS:高层和底层功能

上述函数为请求req的DNS回应的结果节添加一个RR(类型分别为A、AAAA和CNAME)。各个函数中,name是要为之添加结果的主机名,ttl是以秒为单位的存活时间。对于A和AAAA记录,n是要添加的地址个数,addrs是到原始地址的指针:对于A记录,是以n*4字节序列格式给出的IPv4地址;对于AAAA记录,是以n*16字节序列格式给出的IPv6地址。

成功时函数返回0,失败时返回-1。

接口

Libevent参考手册:使用libevent的DNS:高层和底层功能

这个函数为请求的结果节添加一个PTR记录。参数req和ttl跟上面的函数相同。必须提供in(一个IPv4地址)和inaddr_name(一个arpa域的地址)中的一个,而且只能提供一个,以指示为回应提供哪种地址。hostname是PTR查询的答案。

接口

Libevent参考手册:使用libevent的DNS:高层和底层功能

这个函数为请求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中。

接口

Libevent参考手册:使用libevent的DNS:高层和底层功能

evdns_server_request_respond()函数为请求发送DNS回应,带有用户添加的所有RR,以及错误码err。如果不想回应某个请求,可以调用evdns_server_request_drop()来忽略请求,释放请求关联的内存和结构体。

接口

Libevent参考手册:使用libevent的DNS:高层和底层功能

如果要为回应消息设置任何标志,可以在发送回应前的任何时候调用这个函数。

除了evdns_server_request_set_flags()首次在2.0.1-alpha版本中出现外,本节描述的所有函数都在1.3版本中引入。

DNS服务器示例

Libevent参考手册:使用libevent的DNS:高层和底层功能

Libevent参考手册:使用libevent的DNS:高层和底层功能

废弃的DNS接口

废弃的接口

Libevent参考手册:使用libevent的DNS:高层和底层功能

evdns_base_search_ndots_set()等价于使用evdns_base_set_option()设置ndots选项。

除了只能添加IPv4地址的名字服务器外,evdns_base_nameserver_add()函数的行为与evdns_base_nameserver_ip_add()相同。特别的是,evdns_base_nameserver_add()要求网络字节序的四字节地址。

2.0.1-alpha版本之前,不能为DNS服务端口指定event_base。通过evdns_add_server_port()添加的服务端口只能使用默认的event_base。

从版本2.0.1-alpha到2.0.3-alpha,可以使用evdns_set_random_bytes_fn(),而不是evdns_set_transsaction_id_fn(),来指定用于产生随机数的函数。这个函数现在没有效果了,因为libevent有自己的安全的随机数发生器了。

DNS_QUERY_NO_SEARCH标志曾经称作DNS_NO_SEARCH。

2.0.1-alpha版本之前,没有单独的evdns_base记号:evdns子系统中的所有信息都是全局存储的,操作这些信息的函数不需要evdns_base参数。这些函数现在都废弃了,但是还声明在event2/dns_compat.h中。它们通过一个单独的全局evdns_base实现,通过2.0.3-alpha版本引入的evdns_get_global_base()可以访问这个evdns_base。

Libevent参考手册:使用libevent的DNS:高层和底层功能

如果evdns_config_windows_nameservers()可用,则EVDNS_CONFIG_WINDOWS_NAMESERVERS_IMPLEMENTED宏会被定义。

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值