如何解决套接字编程:DIG DNS 查询消息:标头长度不正确?
RFC 参考
我正在从事一个项目,该项目涉及套接字编程和解释 DIG DNS 查询的输出。
我使用 RFC 1035 作为参考。尽管这已经很老了(1987 年),但据我从后来的 RFC(例如 8490)中可以看出,DNS 标头仍然相同。
https://tools.ietf.org/html/rfc1035
代码概览:IPv6 TCP 查询
我用 C 语言编写了一个从 IPv6 TCP 套接字读取的短程序。我使用 DIG 将数据发送到这个套接字。 (我的程序只是读取它在套接字上看到的所有数据,并将其打印到 stdout
。)
请注意,这里有两件不寻常的事情:
- 首先是IPv6的使用
- 其次使用 TCP(DNS 消息通常是 UDP)
这是使用的命令:
dig @::1 -p 8053 duckduckgo.com +tcp
我正在 Debian 测试中运行 dig 版本 DiG 9.16.13-Debian
。 (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”前面有字节 0A
。这是十进制的 10,对应于“duckduckgo”的 10 个字符。此字符串后跟一个字节 03
,对应于“com”的 3 个字节。
字节 0A
的偏移量为 15。不是 12。
我一定误解了 RFC 规范。但我误解了什么?标头本身是否以与我认为的不同的偏移量开始? (字节零。)或者在标题的结尾和第一个问题部分的开头之间是否有一些填充?
本网站现有问题:
评论:下面的链接声明没有填充。这是这个问题的唯一答案。问题是关于DNS 响应而不是查询,并且不询问查询的标头部分。 (虽然其中一方的信息应该适用于另一方,但可能不适用。)
Do DNS messages pad names to an even number of bytes?
评论:以下链接询问了构建数据结构以处理 DNS 数据的最佳方法。此外,答案指出必须注意网络字节顺序和机器字节顺序。我已经意识到这一点,并且在将信息打印到 ntohs()
之前,我使用 x86_64
从网络字节顺序转换为 stdout
字节顺序。这不是问题,也不能解释为什么我看到有关 dns 查询的信息从字节 15 而不是 12 开始,而标头应该是 12 字节的固定大小。
Implementing a DNS Query in c++ according to RFC 1035
解决方法
感谢@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 数据包。 length 的值设置为 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()
用于将大端(网络)字节顺序的数据转换为小端(主机) 字节顺序。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。