C语言实现DNS客户端 | 详解dns_create_question函数的设计与实现

在实现一个简易的 DNS 查询客户端时,构造 DNS 报文是最关键的一步。DNS 报文大致由两个部分组成:

  • Header(报文头)

  • Question(问题)

本文聚焦于 dns_create_question 函数,即如何将用户输入的域名(如 "www.example.com")编码为符合 DNS 协议格式的查询字段,并构造相关的 qtypeqclass 信息。

一、DNS Question 结构体定义回顾 

struct dns_question {
    int length;                 // DNS问询字段总长度(包括name部分)
    unsigned short qtype;      // 查询类型,一般为A记录(0x0001)
    unsigned short qclass;     // 查询类,一般为IN(0x0001)
    unsigned char *name;       // 按DNS格式编码后的域名
};

二、DNS 域名编码格式简介

DNS 协议中对域名的编码方式不同于普通字符串。例如:

  • 输入字符串:www.example.com

  • DNS格式编码如下(十六进制):

03 77 77 77 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00

解释如下:

  • 每个标签前添加一个长度字节(如 "www" 长度为 3)

  • 标签本体("www", "example", "com")

  • 最后以一个 0 字节结尾,表示域名结束

三、dns_create_question 函数实现解析

int dns_create_question(struct dns_question *question, const char *hostname) {
    if (question == NULL || hostname == NULL) return -1;
    memset(question, 0, sizeof(struct dns_question));

    size_t hostlen = strlen(hostname);
    question->name = (unsigned char*)malloc(hostlen + 2);  // +2 是为了保险:包含结尾的0字节
    if (question->name == NULL) return -2;

    question->length = (int)hostlen + 2;

    question->qtype = htons(1);   // A记录,查询IPv4地址
    question->qclass = htons(1);  // IN类,代表Internet

    const char delim[2] = ".";
    unsigned char *qname = question->name;

    // strdup复制hostname,避免strtok破坏原始字符串
    char *hostname_dup = strdup(hostname);
    if (hostname_dup == NULL) {
        free(question->name);
        return -3;
    }

    char *token = strtok(hostname_dup, delim);
    while (token != NULL) {
        size_t len = strlen(token);
        *qname = (unsigned char)len;  // 写入长度前缀
        qname++;

        memcpy(qname, token, len);   // 拷贝标签内容
        qname += len;

        token = strtok(NULL, delim);
    }

    *qname = 0; // DNS要求以0x00结尾标志域名结束

    free(hostname_dup);
    return 0;
}

四、核心步骤解析

1. 内存分配与安全检查

question->name = (unsigned char*)malloc(hostlen + 2);

这里 +2 是为保证:

  • 结尾的 0 字节(必须)

  • 至少一个 . 的情况下也有余量

2.域名转为DNS格式

DNS 协议中使用“长度 + 标签”的格式编码域名:

例如 www.example.com 变成:

[3] w w w [7] e x a m p l e [3] c o m [0]

每一段前都要加一个长度字节,结尾加 0x00 表示结束。这是 DNS 报文格式的核心要求。

3.设置查询类型与类

question->qtype = htons(1);
question->qclass = htons(1);
  • htons() 是“host to network short”的缩写,把主机字节序(Little Endian)转为网络字节序(Big Endian)。

  • qtype = 1 表示 A记录查询(IPv4 地址)

  • qclass = 1 表示 IN类,即 Internet 网络查询

这些值在大多数实际 DNS 查询中是默认的标准设置。

4. hostname 副本分割

const char delim[2] = ".";
unsigned char *qname = question->name;

char *hostname_dup = strdup(hostname);
if (hostname_dup == NULL) {
    free(question->name);
    return -3;
}
  • 分隔符设置:DNS 域名由 . 分隔,如 www.example.com 分成三段。

  • 复制 hostname

    • 因为 strtok() 函数会修改原字符串(它会把 . 替换成 \0),所以使用 strdup() 创建一份副本。

  • 异常处理

    • 如果 strdup 失败,释放之前已分配的 name 内存,并返回 -3,防止内存泄漏。

5.将域名转换为 DNS 编码格式

char *token = strtok(hostname_dup, delim);
while (token != NULL) {
    size_t len = strlen(token);
    *qname = (unsigned char)len;  // 写入长度前缀
    qname++;

    memcpy(qname, token, len);   // 拷贝标签内容
    qname += len;

    token = strtok(NULL, delim);
}

这是整段函数中最核心的部分 —— 将普通域名字符串转换为 DNS 报文格式的 QNAME

举个例子,www.example.com 会被编码成如下形式:

03 77 77 77 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
  • *qname = len:每段前加长度字节。

  • memcpy(qname, token, len):将每段标签拷贝到缓冲区。

  • qname += len:更新写入指针。

循环直到没有更多 label。

https://github.com/0voice

你可能感兴趣的:(服务器,网络,linux)