linux - 套接字编程:DIG DNS 查询消息:标头长度不正确?
问题描述
RFC 参考
我正在从事一个项目,该项目涉及套接字编程和解释 DIG DNS 查询的输出。
我使用 RFC 1035 作为参考。尽管据我从后来的 RFC(例如 8490)中可以看出,这已经很老了(1987 年),但 DNS 标头仍然是相同的。
https://www.rfc-editor.org/rfc/rfc1035
代码概述:IPv6 TCP 查询
我用 C 语言编写了一个从 IPv6 TCP 套接字读取的简短程序。我使用 DIG 将数据发送到此套接字。(我的程序只是读取它在套接字上看到的所有数据,并将其打印到stdout
.)
请注意,这里有两件不寻常的事情:
- 首先使用 IPv6
- 其次使用TCP(DNS报文往往是UDP)
这是使用的命令:
dig @::1 -p 8053 duckduckgo.com +tcp
我DiG 9.16.13-Debian
在 Debian 测试上运行 dig version 。(cera 2021-5月)
输出、讨论和问题
这是从套接字读取的十六进制和可打印字符输出:
Hex:
00 37 61 78 01 20 00 01 00 00 00 00 00 01 0A 64 75 63 6B 64 75 63 6B 67 6F 03 63 6F 6D 00 00 01 00 01 00 00 29 10 00 00 00 00 00 00 0C 00 0A 00 08 00 7A 4* 48 2C 16 0* 33
Char:
00 7 61 x 01 20 00 01 00 00 00 00 00 01 0A d u c k d u c k g o 03 c o m 00 00 01 00 01 00 00 ) 10 00 00 00 00 00 00 0C 00 0A 00 08 00 z 4* H , 16 0* 33
如果遇到不可打印的字符,则会打印十六进制值。
虽然这是一个相当长的数据流,但问题与标题的长度有关。
根据 RFC 1035,标头的长度应为 12 个字节。
4.1.1. Header section format
The header contains the following fields:
1 1 1 1 1 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ID |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR| Opcode |AA|TC|RD|RA| Z | RCODE |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| QDCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ANCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| NSCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ARCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
标头后跟一个QUESTION SECTION
. 问题部分以指定长度的单个字节开始。
检查上面的数据流,我们看到偏移量 12 处的字节的值为 0。我在下面用偏移量数字重复它以使其清楚。数据在中间一行,上下一行是字节偏移量。
0 1 2 3 4 5 6 7 8 9 10 11 <- byte 12
00 37 61 78 01 20 00 01 00 00 00 00 00 01 0A 64 75 63 6B ...
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <- byte 15
这显然没有任何意义。
再次查看流,我们可以看到“duckduckgo”前面是 byte 0A
。这是十进制的 10,对应“duckduckgo”的 10 个字符。该字符串后跟一个字节03
,对应于“com”的 3 个字节。
字节的偏移量0A
是 15。不是 12。
我一定误解了 RFC 规范。但是我误解了什么?标头本身的偏移量是否与我认为的不同?(零字节。)或者在标题的结尾和第一个问题部分的开头之间可能有一些填充?
本站现有问题:
评论:下面的链接指出没有 padding。这是这个问题的唯一答案。问题是关于DNS 响应而不是查询,并且不询问查询的标头部分。(虽然来自一方的信息可能适用于另一方,但可能不适用。)
评论:下面的链接询问了构建数据结构来处理 DNS 数据的最佳方法。此外,答案指出,必须注意网络字节顺序和机器字节顺序。我已经意识到这一点,并且ntohs()
在将x86_64
信息打印到stdout
. 这不是问题,也没有解释为什么我看到有关 dns 查询的信息从字节 15 而不是 12 开始,而标头应该是 12 字节的固定大小。
解决方案
感谢@SteffenUllrich 在评论中提示解决方案。
RFC 1035 4.2.2 状态
4.2.2. TCP usage
Messages sent over TCP connections use server port 53 (decimal). The
message is prefixed with a two byte length field which gives the message
Mockapetris [Page 32]
RFC 1035 Domain Implementation and Specification November 1987
length, excluding the two byte length field. This length field allows
the low-level processing to assemble a complete message before beginning
to parse it.
在某些时候,我已经删除了结构开头的 2 字节字段。
这是重新启用 2 字节长度字段后的结构。
struct __attribute__((__packed__)) dns_header
{
unsigned short ID;
union
{
unsigned short FLAGS;
struct
{
unsigned short QR : 1;
unsigned short OPCODE : 4;
unsigned short AA : 1;
unsigned short TC : 1;
unsigned short RD : 1;
unsigned short RA : 1;
unsigned short Z : 3;
unsigned short RCODE : 4;
};
};
unsigned short QDCOUNT;
unsigned short ANCOUNT;
unsigned short NSCOUNT;
unsigned short ARCOUNT;
};
struct __attribute__((__packed__)) dns_struct_tcp
{
unsigned short length; // length excluding 2 bytes for length field
struct dns_header header;
};
例如:我收到一个长度为 53 字节的 TCP 数据包。长度的值设置为 51。
要将数据读入此结构:
memcpy(&dnsdata, buf, sizeof(struct dns_struct_tcp));
要解释此数据(因为它以网络字节顺序存储):
void dns_header_print(FILE *file, const struct dns_header *header)
{
fprintf(file, "ID: %u\n", ntohs(header->ID));
char str_FLAGS[8 * sizeof(unsigned short) + 1];
str_FLAGS[8 * sizeof(unsigned short)] = '\0';
print_binary_16_fixed_width(str_FLAGS, header->FLAGS);
fprintf(file, "FLAGS: %s\n", str_FLAGS);
fprintf(file, "FLAGS: QOP ATRRZZZR \n");
fprintf(file, " RCODEACDA CODE\n");
fprintf(file, "QDCOUNT: %u\n", ntohs(header->QDCOUNT));
fprintf(file, "ANCOUNT: %u\n", ntohs(header->ANCOUNT));
fprintf(file, "NSCOUNT: %u\n", ntohs(header->NSCOUNT));
fprintf(file, "ARCOUNT: %u\n", ntohs(header->ARCOUNT));
}
请注意,标志不变,因为标志的每个字段的长度小于 8 位。然而在x86_64
系统上,unsigned short
以小端格式存储,因此ntohs()
用于将大端(网络)字节顺序的数据转换为小端(主机)字节顺序。
推荐阅读
- javascript - 如何将响应对象的属性与新对象一起返回?
- android - 如何删除文件 API 30
- tableau-api - 如何在 tableau 中使用 mann whitney u 检验确定统计显着性
- javascript - 太多的重新渲染。React 限制了渲染的数量?Socket.io 聊天应用
- python - 如何从包含嵌套字典的字典创建 Pandas 数据框?
- html - 没有任何风格的形式
- awk - 如何使用 awk 格式化程序的输出,转换第一列但保持其余部分不变?
- deployment - 部署到 Azure 应用服务时出现 Azure Key Vault 依赖项错误
- java - 使用构造函数计算两点之间的欧几里得距离
- scala - Scala - 未来的“内存不足”