关于D-Bus的文章中曾介绍了如何通过D-Bus调用系统服务从而实现解析出一个域名的IP地址的过程,本文我们继续调用系统调用来实现解析任意DNS记录,系统调用的方法与前一篇文章类似,只是方法名称和调用参数以及返回参数不同,本文将详细介绍systemd-resolved服务中的ResolveRecord方法,同前面几篇关于D-BUS的文章相同,本文将使用 libdbus 库实现系统服务的调用,本文给出了实现解析任意DNS记录的实例,附有完整的源代码;本文实例在 Ubuntu 20.04 上编译测试通过,gcc版本号为:9.4.0;本文不适合 Linux 编程的初学者阅读。
在阅读本文之前,建议阅读 《IPC之十三:使用libdbus通过D-Bus请求系统调用实现域名解析的实例》 和文章 《用C语言实现的一个DNS客户端》;
前一篇参考文章里介绍了如何使用 libdbus 库调用系统调用的概念和方法,本文的调用的系统调用与该文中一样,均是 systemd-resoled
,只是请求的方法不同,所以,调用参数和返回参数均不同,但很多概念是一样的;
后一篇参考文章里介绍了 DNS 的基本概念,了解 DNS 基本概念是理解本文的基础,否则可能云里雾里搞不明白;
要通过 D-Bus 调用一个系统服务中的方法,要了解以下一些信息:
D-Bus 的类型系统(Type System)简单回顾,这部分在 D-Bus Specification 中有详细的描述,这里把一些关键信息列出来
普通数据类型
Conventional name | ASCII type-code | Encoding |
---|---|---|
BYTE | y (121) | Unsigned 8-bit integer |
BOOLEAN | b (98) | Boolean value: 0 is false, 1 is true, any other value allowed by the marshalling format is invalid |
INT16 | n (110) | Signed (two’s complement) 16-bit integer |
UINT16 | q (113) | Unsigned 16-bit integer |
INT32 | i (105) | Signed (two’s complement) 32-bit integer |
UINT32 | u (117) | Unsigned 32-bit integer |
INT64 | x (120) | Signed (two’s complement) 64-bit integer (mnemonic: x and t are the first characters in “sixty” not already used for something more common) |
UINT64 | t (116) | Unsigned 64-bit integer |
DOUBLE | d (100) | IEEE 754 double-precision floating point |
UNIX_FD | h (104) | Unsigned 32-bit integer representing an index into an out-of-band array of file descriptors, transferred via some platform-specific mechanism (mnemonic: h for handle) |
字符串类型
Conventional name | ASCII type-code | Validity constraints |
---|---|---|
STRING | s (115) | No extra constraints |
OBJECT_PATH | o (111) | Must be a syntactically valid object path |
SIGNATURE | g (103) | Zero or more single complete types |
struct 的类型代码为 ‘r’,但在实际表达上通常用括号 “( )” 表达,比如:(ii)
表示有两个整数类型的结构,在结构中还可以嵌套另一个结构,比如:“(i(ii))”;
数组的类型代码为 ‘a’,ai
表示一个整数数组,相当于 int a[]
,也可以定义一个结构数组,比如:a(ii)
表示一个有两个整数的结构数组;
DNS 服务遵循 RFC-1035 所描述的规范,其返回的数据被称为 RR(Resource Record),其格式为:
1 1 1 1 1 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| |
/ /
/ NAME /
| |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| TYPE |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| CLASS |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| TTL |
| |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| RDLENGTH |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
/ RDATA /
/ /
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
RR 中所有表示域名的地方均以"标签(label)"方式表示,每个标签的最大长度为 63 个字符,域名的最大长度为 255 个字符;
一个标签的第一个字符的低 6 位表示这个标签的长度,后面紧跟相应数量的字符的 ASCII,一个长度为 0 的标签作为一个域名的结束,简单的说就是遇到一个 "\0"
表示一个域名结束;
一个域名可以由多个标签组成,标签之间以字符 "."
分割;
RR 所有数字变量的字节序均为 “Big Endian”,而 X86 架构的字节序为 “Little Endian”,所以在读取数字变量时需要做一下转换。
systemd-resolved 在 D-Bus 上提供了一组用于解析 DNS 记录的 API,如下:
ResolveHostname()
用于解析主机名以获取其 IP 地址;ResolveAddress()
用于反向操作,获取 IP 地址的主机名;ResolveService()
用于解析 DNS-SD(DNS Service Discovery) 或 SRV 服务ResolveRecord()
用于解析任意资源记录(RR)关于这些 API 的说明,可以点击这里 org.freedesktop.resolve1 APIs
其实 glibc 中也是有类似的功能调用的,比如:getaddrinfo() 和 getnameinfo();
本文中,将使用 systemd-resolved
的 ResolveRecord()
调用,解析域名的 A 记录、CNAME 记录和 MX 记录;
在 systemd-resolved 官方文档 中对 ResolveRecord 方法做了如下定义:
node /org/freedesktop/resolve1 {
interface org.freedesktop.resolve1.Manager {
methods:
......
ResolveRecord(in i ifindex,
in s name,
in q class,
in q type,
in t flags,
out a(iqqay) records,
out t flags);
......
}
}
根据 org.freedesktop.resolve1 APIs 的说明:
服务名称(总线名称):org.freedesktop.resolve1
对象路径:/org/freedesktop/resolve1
接口名称:org.freedesktop.resolve1.Manager
方法名称:ResolveRecord
输入参数 5 个,分别为:
序号 | 数据类型 | 名称 | 说明 |
---|---|---|---|
1 | int32_t | ifindex | 网络接口索引号,0-任意接口 |
2 | char * | name | 要查询的域名 |
3 | uint16_t | class | 要查询地址类型,1-IN(internet) |
4 | uint16_t | type | 要查询的记录类型,1-A,5-CNAME,15-MX |
5 | uint64_t | flags | 标志位,置 0 即可 |
输出参数 2 个:
结构数组,参考下面结构
序号 | 数据类型 | 名称 | 说明 |
---|---|---|---|
1 | int32_t | ifindex | 实际使用的网络接口索引号 |
2 | uint16_t | class | 记录地址类型,与输入参数一致 |
3 | uint16_t | type | 记录类型,与输入参数一致 |
4 | char array | rrdata | RR 记录,符合 RFC-1035 描述的 RR 的格式 |
64 位无符号整数(uint64_t),标志位,应该为 1,表示使用的是经典单播 DNS 协议进行的 DNS 查询,详情请参考 org.freedesktop.resolve1 APIs;
关于输出参数的 rrdata 字段,遵循 RFC-1035 中 3.3
节的描述,在本文上一节中有简单介绍,在文章 《用C语言实现的一个DNS客户端》 中有比较详细的介绍和实例源程序;
RR 记录中有如下字段:NAME、TYPE、CLASS、RDLENGTH 和 RDATA,在本文上一节中有简要说明;
源程序:dbus-dns-record.c (点击文件名下载源程序,建议使用UTF-8字符集)演示了使用 libdbus 通过 D-Bus 请求系统调用实现查找 DNS 任意记录的方法;
源程序中有较为详细的注释,这里就不做太多解释了
编译:gcc -Wall -g dbus-dns-record.c -o dbus-dns-record `pkg-config --libs --cflags dbus-1`
有关 pkg-config --libs --cflags dbus-1
可以参阅文章 《IPC之十一:使用D-Bus实现客户端向服务端请求服务的实例》 中的简要说明;
运行:./dbus-dns-record <domain name> [A/CNAME/MX]
,两个参数,第一个参数是要查询的域名,第二个参数是要查询的记录类型,本程序仅支持三种记录类型:A 记录、CNAME 记录和 MX 记录;
./dbus-dns-record baidu.com MX
运行截图: