本文聚焦讲解如何通过 C 语言构造并发送一个最小化的 DNS 请求,特别以
dns_client_commit()
函数为主线,带你一步步理解 DNS 请求的构造过程。
我们平时在浏览器里输入一个网址(比如 www.baidu.com
),浏览器其实背后会通过操作系统的 DNS 模块发送一个查询请求,将域名解析为 IP 地址。
而如果我们手动用 C 语言自己构造 DNS 请求,我们可以更深刻地理解底层网络通信的细节,比如 UDP 套接字、报文格式、DNS 协议结构等。
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 最常用的传输方式,简单快速,适合小数据量通信。
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 为二进制,便于系统识别。
struct dns_header header = {0};
dns_create_header(&header);
报文头结构体填入了如下字段:
随机 ID:标记请求和响应是否匹配;
标志位:设置为 0x0100
(标准查询,递归);
查询数量设为 1。
所有字段都转为 网络字节序(用 htons()
)是网络编程常识。
struct dns_question question = {0};
dns_create_question(&question, domain);
将传入的 domain
字符串,如 www.baidu.com
,转换为 DNS 格式的 QNAME:
03 77 77 77 05 62 61 69 64 75 03 63 6f 6d 00
每段前面加一个长度字节,末尾以 0x00
结尾。
同时设置 QTYPE=1(A记录)、QCLASS=1(IN 类)。
char request[1024] = {0};
int length = dns_build_requestion(&header, &question, request);
该函数负责将 header + question 拼接到请求缓冲区中;
返回实际请求报文的长度(单位:字节);
request
就是我们最终要发出去的数据。
sendto(sockfd, request, length, 0, (struct sockaddr*)&servaddr, addr_len);
使用 sendto()
直接把 DNS 报文发往目标服务器;
无需建立连接;
这是 UDP 的典型使用方式。
┌─────────────┐
│ 输入域名 │
└────┬────────┘
↓
┌──────────────────────┐
│ 创建 UDP socket │
└────┬─────────────────┘
↓
┌──────────────────────┐
│ 填充服务器地址结构 │
└────┬─────────────────┘
↓
┌──────────────────────┐
│ 构造 DNS Header │
└────┬─────────────────┘
↓
┌──────────────────────┐
│ 构造 DNS Question │
└────┬─────────────────┘
↓
┌──────────────────────┐
│ 拼接完整报文 │
└────┬─────────────────┘
↓
┌──────────────────────┐
│ 发送 UDP 请求 │
└────┬─────────────────┘
↓
┌──────────────────────┐
│ 关闭 socket │
└──────────────────────┘
#include
#include
#include
#include
#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);
}
https://github.com/0voice