本文聚焦讲解如何通过 C 语言构造并发送一个最小化的 DNS 请求,特别以
dns_client_commit()
函数为主线,带你一步步理解 DNS 请求的构造过程。
为什么要学习 DNS 报文构造?
我们平时在浏览器里输入一个网址(比如 www.baidu.com
),浏览器其实背后会通过操作系统的 DNS 模块发送一个查询请求,将域名解析为 IP 地址。
而如果我们手动用 C 语言自己构造 DNS 请求,我们可以更深刻地理解底层网络通信的细节,比如 UDP 套接字、报文格式、DNS 协议结构等。
第一步:创建 UDP 套接字
cpp
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
return -1;
}
-
socket(AF_INET, SOCK_DGRAM, 0)
:创建一个基于 IPv4 的 UDP 套接字。 -
SOCK_DGRAM
指的是数据报套接字(即 UDP)。 -
创建失败直接返回错误。
UDP 是 DNS 最常用的传输方式,简单快速,适合小数据量通信。
第二步:配置服务器地址结构
cpp
struct sockaddr_in servaddr = {0};
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(DNS_SERVER_PORT);
servaddr.sin_addr.s_addr = inet_addr(DNS_SERVER_IP);
socklen_t addr_len = sizeof(servaddr);
-
sin_family
设置为AF_INET
表示 IPv4; -
sin_port
使用htons
转换为网络字节序的端口(这里是 53); -
sin_addr.s_addr
是将字符串 IP 地址"114.114.114.114"
转为 32 位网络地址。
使用 inet_addr()
转换字符串 IP 为二进制,便于系统识别。
第三步:创建 DNS 报文头部结构
cpp
struct dns_header header = {0};
dns_create_header(&header);
-
报文头结构体填入了如下字段:
-
随机 ID:标记请求和响应是否匹配;
-
标志位:设置为
0x0100
(标准查询,递归); -
查询数量设为 1。
-
所有字段都转为 网络字节序 (用 htons()
)是网络编程常识。
第四步:构造域名查询部分(Question 区)
cpp
struct dns_question question = {0};
dns_create_question(&question, domain);
将传入的 domain
字符串,如 www.baidu.com
,转换为 DNS 格式的 QNAME:
cpp
03 77 77 77 05 62 61 69 64 75 03 63 6f 6d 00
-
每段前面加一个长度字节,末尾以
0x00
结尾。 -
同时设置 QTYPE=1(A记录)、QCLASS=1(IN 类)。
第五步:构造完整 DNS 请求报文
cpp
char request[1024] = {0};
int length = dns_build_requestion(&header, &question, request);
-
该函数负责将 header + question 拼接到请求缓冲区中;
-
返回实际请求报文的长度(单位:字节);
-
request
就是我们最终要发出去的数据。
第六步:发送 UDP 请求
cpp
sendto(sockfd, request, length, 0, (struct sockaddr*)&servaddr, addr_len);
-
使用
sendto()
直接把 DNS 报文发往目标服务器; -
无需建立连接;
-
这是 UDP 的典型使用方式。
小结:dns_client_commit 的完整流程图
cpp
┌─────────────┐
│ 输入域名 │
└────┬────────┘
↓
┌──────────────────────┐
│ 创建 UDP socket │
└────┬─────────────────┘
↓
┌──────────────────────┐
│ 填充服务器地址结构 │
└────┬─────────────────┘
↓
┌──────────────────────┐
│ 构造 DNS Header │
└────┬─────────────────┘
↓
┌──────────────────────┐
│ 构造 DNS Question │
└────┬─────────────────┘
↓
┌──────────────────────┐
│ 拼接完整报文 │
└────┬─────────────────┘
↓
┌──────────────────────┐
│ 发送 UDP 请求 │
└────┬─────────────────┘
↓
┌──────────────────────┐
│ 关闭 socket │
└──────────────────────┘
完整代码
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define DNS_SERVER_PORT 53
#define DNS_SERVER_IP "114.114.114.114"
struct dns_header{
unsigned short id;
unsigned short flags;
unsigned short questions;
unsigned short answers;
unsigned short authority;
unsigned short additional;
};
struct dns_question{
int length;
unsigned short qtype;
unsigned short qclass;
unsigned char *name;
};
int dns_create_header(struct dns_header *header){
if(header == NULL) return -1;
memset(header,0,sizeof(struct dns_header));
srand((unsigned int)time(NULL));
header->id = (unsigned short)rand();
header->flags = htons(0x0100);
header->questions = htons(1);
return 0;
}
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);
if(question->name == NULL) return -2;
question->length = (int)hostlen + 2;
question->qtype = htons(1);
question->qclass = htons(1);
//name
const char delim[2] = ".";
unsigned char *qname = question->name;
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; // QNAME 结尾补0
free(hostname_dup);
return 0;
}
int dns_client_commit(const char *domain){
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if (sockfd < 0)
{
return -1;
}
struct sockaddr_in servaddr = {0};
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(DNS_SERVER_PORT);
servaddr.sin_addr.s_addr = inet_addr(DNS_SERVER_IP);
socklen_t addr_len = sizeof(servaddr);
struct dns_header header = {0};
dns_create_header(&header);
struct dns_question question = {0};
dns_create_question(&question,domain);
char request[1024] = {0};
int length = dns_build_requestion(&header,&question,request);
// request
sendto(sockfd,request,length,0,(struct sockaddr*)&servaddr,addr_len);
close(sockfd);
}