首页 > 解决方案 > 套接字编程: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.)

请注意,这里有两件不寻常的事情:

这是使用的命令:

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消息是否将名称填充到偶数字节?

评论:下面的链接询问了构建数据结构来处理 DNS 数据的最佳方法。此外,答案指出,必须注意网络字节顺序和机器字节顺序。我已经意识到这一点,并且ntohs()在将x86_64信息打印到stdout. 这不是问题,也没有解释为什么我看到有关 dns 查询的信息从字节 15 而不是 12 开始,而标头应该是 12 字节的固定大小。

根据 RFC 1035 在 C++ 中实现 DNS 查询

标签: linuxsocketsnetwork-programmingrfcdig

解决方案


感谢@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()用于将大端(网络)字节顺序的数据转换为小端(主机)字节顺序。


推荐阅读