当前位置: 首页 > news >正文

Linux: C语言解析域名

在上一篇博客 Linux: C语言发起 DNS 查询报文 中,自己构造 DNS 查询报文,发出去,接收响应,以二进制形式把响应的数据写入文件并进行分析。文章的最后留下一个悬念,就是写代码解析 DNS answer section 部分。本文来完成解析应答报文的代码。

当我们使用浏览器访问某个网站的时候,浏览器拿到 URL 后,会解析 URL,拿到网站的域名,然后再进行 DNS 解析,拿到这个网站域名对应服务器的 IP 地址。然后使用网站服务器的 IP 地址和服务器建立一个 TCP 连接。再往后还有 SSL/TLS 握手等等操作,然后是交换数据。

不止是浏览器访问网站,很多情景下都会用到 DNS 解析。

DNS 协议多数情况下使用 UDP 协议进行通信,有的时候也会使用 TCP 进行通信(传输大量数据)。

DNS 协议使用 53 号端口。

Talk is cheap, show code:

//dnr.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <ctype.h>  // for isprint()#define VERSION "1.0.0"
#define DNS_SERVER "8.8.8.8"  // Google's public DNS server
#define DNS_PORT 53 // DNS uses port 53// 生成随机的 16 位事务 ID
unsigned short generate_random_id() {srand(time(NULL));  // 设置随机数种子(基于当前时间)return (unsigned short)(rand() % 65536);  // 生成 0 到 65535 的随机数
}// DNS 头部结构体
struct DNSHeader {unsigned short id; // Transaction IDunsigned short flags; // DNS flagsunsigned short qdcount; // Number of questionsunsigned short ancount; // Number of answersunsigned short nscount; // Number of authority recordsunsigned short arcount; // Number of additional records
};// DNS 查询部分
struct DNSQuestion {unsigned short qtype; // Query type (A, MX, etc.)unsigned short qclass; // Query class (IN, etc.)
};// DNS Resource Record structure
struct DNSRecord {unsigned short type;   // Record Type (A, CNAME, etc.)unsigned short class_; // Class (IN)unsigned int ttl;      // TTL (Time to Live)unsigned short rdlength; // Length of the record dataunsigned char rdata[];  // Record data (IP address for A record)// unsigned char *rdata;      // *(DNSRecord->rdata + 1)  ===> Segmentation fault (core dumped)
};// 构建 DNS 查询报文
int build_dns_query(char *query, const char *hostname, int pos) {char *label;for (label = strtok(strdup(hostname), "."); label != NULL; label = strtok(NULL, ".")) {query[pos++] = strlen(label);strcpy(query + pos, label);pos += *(query + pos - 1);}query[pos++] = 0;struct DNSQuestion question = { htons(1), htons(1) };      // For CNAME use "{ htons(5), htons(1)}"";memcpy(query + pos, &question, sizeof(question));return (pos + sizeof(question));
}// 打印十六进制数据,每行显示 16 个字节
void print_hex(const unsigned char *data, size_t length) {for (size_t i = 0; i < length; i++) {// 每行打印 16 个字节if (i % 16 == 0) {// 打印行号偏移 (16进制格式)printf("%08zx: ", i);       // z 长度修饰符表示接下来要输出的是一个size_t类型的值。size_t是一个无符号整数类型}// 打印当前字节的十六进制表示printf("%02x ", data[i]);// 每行结束时,打印字符表示(可打印字符显示,其他显示点 '.')if (i % 16 == 15) {// 如果是当前行最后一个字节printf("    ");for (size_t j = i - (i % 16); j <= i; j++) {if (isprint(data[j])) {printf("%c", data[j]);} else {printf(".");}}printf("\n");  // 换行} else if (i == length - 1) {//或者是最后一行for (size_t k = 0; k < (16 - (length % 16)); k++)printf("   ");printf("    ");for (size_t j = i - (i % 16); j <= i; j++) {if (isprint(data[j])) {printf("%c", data[j]);} else {printf(".");}}printf("\n");  // 换行}}
}// Parse DNS Answer Section
void parse_answer(const unsigned char *data, size_t len) {struct DNSRecord *answer = (struct DNSRecord *)(data);const unsigned short TYPE = ntohs(answer->type);const unsigned short CLASS = ntohs(answer->class_);const unsigned int TTL = ntohl(answer->ttl);const unsigned short RDLENGTH = ntohs(answer->rdlength);// Print TYPE, CLASS, TTL, and RDLENGTHprintf("TYPE: ");switch (TYPE) {case 1:  // A Recordprintf("A\n");break;case 5:  // CNAME Recordprintf("CNAME\n");break;default:printf("%d\n", TYPE);  // For other record types, just print the numberbreak;}printf("CLASS: ");switch (CLASS) {case 1:  // IN (Internet)printf("IN\n");break;default:printf("%d\n", CLASS);  // For other classes, just print the numberbreak;}printf("TTL: %d\n", TTL);printf("RDLENGTH: %d\n", RDLENGTH);// Handle RDATA based on the record typeif (TYPE == 1) {  // A record (IPv4 Address)if (RDLENGTH == 4) {  // RDATA for A record should always be 4 bytes (IPv4 address)unsigned char *ip = answer->rdata;printf("RDATA (IP Address): %u.%u.%u.%u\n",(unsigned char) *(answer->rdata), (unsigned char) *(answer->rdata + 1), (unsigned char) *(answer->rdata + 2), (unsigned char) *(answer->rdata + 3));} else {printf("Invalid RDLENGTH for A record\n");}} else if (TYPE == 5) {  // CNAME record// CNAME is a domain name, it is stored as a series of labels// rdata points to the domain name, so print itprintf("RDATA (CNAME): ");unsigned char *cname = answer->rdata;// DNS names are in "label" format, so we need to handle them accordinglywhile (*cname != 0) {int label_length = *cname;  // Length of the labelcname++;for (int i = 0; i < label_length; i++) {printf("%c", cname[i]);}cname += label_length;if (*cname != 0) {printf(".");}}printf("\n");} else {printf("RDATA: Raw Data\n");// For other record types, just print the raw data (for debugging purposes)print_hex(answer->rdata, RDLENGTH);}putchar('\n');
}// DNS request
char* dns_request(char *hostname, const char *DNS_Server) {printf("\ndnr version %s\n\n", VERSION);char query[512] = { 0 };// 设置 DNS 请求头unsigned short id = generate_random_id();struct DNSHeader header = { htons(id), htons(0x0100), htons(1), htons(0), htons(0), htons(0) };memcpy(query, &header, sizeof(header));int pos;pos = build_dns_query(query, hostname, sizeof(header));printf("Query:\n");print_hex(query, pos);int sockfd;struct sockaddr_in server_addr;sockfd = socket(AF_INET, SOCK_DGRAM, 0);   // UDPif (sockfd < 0) {return "Socket creation failed";}memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(DNS_PORT);if (DNS_Server == NULL)server_addr.sin_addr.s_addr = inet_addr(DNS_SERVER);elseserver_addr.sin_addr.s_addr = inet_addr(DNS_Server);if (sendto(sockfd, query, sizeof(query), 0, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {close(sockfd);return "Sendto failed";}char buffer[512] = { 0 };socklen_t len = sizeof(server_addr);int n = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&server_addr, &len);if (n < 0) {close(sockfd);return "Recvfrom failed";}printf("\n\nReceived %d bytes from DNS server.\n\n", n);printf("Response:\n");print_hex(buffer, n);printf("\n\nAnswer Section:\n");for (int i = pos; i < n; i++)printf("%02x ", (unsigned char) *(buffer + i));printf("\n\nAnalysis:\n");struct DNSHeader *resHeader = NULL;resHeader = (struct DNSHeader *)buffer;//memcpy(resHeader, buffer, sizeof(struct DNSHeader));printf("Number of questions: %d\n", ntohs(resHeader->qdcount));printf("Number of answers: %d\n", ntohs(resHeader->ancount));printf("Number of authority records: %d\n", ntohs(resHeader->nscount));printf("Number of additional records: %d\n", ntohs(resHeader->arcount));if (0 != ntohs(resHeader->ancount)) {     // Number of answers is not zero.while (pos < n) {unsigned short RDlen;memcpy(&RDlen, (buffer + pos + 10), 2);RDlen = ntohs(RDlen);parse_answer(buffer + pos + 2, 10 + RDlen);pos += (12 + RDlen);}}close(sockfd);
}int main(int argc, char* argv[]) {if (argc < 2 || !(strcmp("-h", argv[1])) || !(strcmp("-help", argv[1]))) {fprintf(stderr, "\n%s version %s\n\n\tAuthor: Jackey Song\n\n\tDescription: Get the IP addresses corresponding to the domain names.\n\n\tUsage:\n\t  %s <hostname_1> <hostname_2> <hostname_3> ...\n\t  %s -s <DNS_Server_IP_Address> <hostname_1> <hostname_2> <hostname_3> ...\n\n",argv[0], VERSION, argv[0], argv[0]);fprintf(stderr, "-----------------------------------------------------------------------------------------------\n\n%s 版本 %s\n\n\t作者: Jackey Song\n\n\t描述: 获取与域名对应的IP地址。\n\n\t用法:\n\t  %s <hostname_1> <hostname_2> <hostname_3> ...\n\t  %s -s <DNS_Server_IP_Address> <hostname_1> <hostname_2> <hostname_3> ...\n\n",argv[0], VERSION, argv[0], argv[0]);return 1; // 如果没有提供主机名,打印帮助信息并退出}else if (!(strcmp("-s", argv[1]))) {for (int i = 3; i < argc; i++)dns_request(argv[i], argv[2]);}else {for (int i = 1; i < argc; i++) {dns_request(argv[i], NULL);}}return 0;
}

编译器仍然是 gccgcc -o dnr dnr.c 编译后的二进制文件为 dnr
运行:./dnr -h & ./dnr -help 显示帮助信息。

请添加图片描述

解析 baidu.com 和 jackey-song.com :

./dnr baidu.com jackey-song.com

请添加图片描述

请添加图片描述

查询 www.baidu.com CNAME 记录:

要修改代码 struct DNSQuestion question = { htons(1), htons(1) }; // For CNAME use "{ htons(5), htons(1)}"";

./dnr -s 192.168.3.1 baidu.com,这里的 -s 可以指定 DNS 服务器的 IP 地址,192.168.3.1 是我本地 WIFI 路由器的 IP 地址,路由器配置 DNS 后,相当于是一个本地 DNS 服务器。如果不使用 -s 来指定 DNS 服务器,代码中会使用默认的 DNS 服务器,8.8.8.8 Google 公共 DNS 服务器。

请添加图片描述

从打印的结果可以看到 www.baidu.com 别名为 www.a.shifen.,其实我的代码中还没有完善,Response 的最后两个字节是 c0 16 ,这是一个指针,十六进制数 16 转换成十进制数就是 22,也就是说 www.a.shifen. 后面还有一部分,在整个 Response 的偏移量 22 位置处,偏移量下标从 0 开始,第 22 位置就是 03 63 6f 6d (com),所以 www.baidu.com 完整的别名就是 www.a.shifen.com




代码中需要注意的地方:

struct DNSRecord 的定义这里,一开始我使用的是 unsigned char *rdata,当我使用指针 *(DNSRecord->rdata + 1) 操作的时候会出现错误 Segmentation fault (core dumped) 。 这是因为 rdata 被定义为指向 unsigned char 的指针(unsigned char *rdata)。这样的话,rdata 只是一个指针,并没有分配内存来存储 DNS 记录的数据。使用 unsigned char rdata[]; 柔性数组(变长数组类型)就可以解决使用指针操作结构体成员变量内存泄露的问题。

    unsigned char rdata[];  // Record data (IP address for A record)// unsigned char *rdata;      // *(DNSRecord->rdata + 1)  ===> Segmentation fault (core dumped)

parse_answer() 函数中,我一开始使用的是 memcpy(answer, data, len) 来进行内存操作,仍然会出现内存泄露的问题。

// Parse DNS Answer Section
void parse_answer(const unsigned char *data, size_t len) {struct DNSRecord *answer = (struct DNSRecord *)(data);//struct DNSRecord *answer;//memcpy(answer, data, len);  // printf("%s", answer->rdata); ===> Segmentation fault (core dumped)

(struct DNSRecord*)(data) 只是将 data 指针转换为 struct DNSRecord* 类型,告诉编译器 data 实际上是一个指向 struct DNSRecord 类型数据的指针。这种操作不会更改内存内容,只是改变了指针的解释方式。这是安全的,前提是 data 本身确实指向 struct DNSRecord 类型的数据(即它指向的数据布局与 struct DNSRecord 一致)。

memcpy(answer, data, len)data 中的内容复制到 answer 中,假设 answer 已经是一个有效的指针,指向了足够的内存空间,能够容纳 len 字节数据。如果 answer 指向了非法的或未初始化的内存,或者 len 超出了 answer 可以承受的内存空间,就会发生访问违规,导致 segmentation fault

注意网络字节序 使用 大端字节序 Big Endian,而有的主机使用小端字节序 Little Endian,htons() 主机字节序转换成网络字节序。ntohs() 网络字节序转换成主机字节序。

  • 在大端字节序中,高位字节存储在低地址处,低位字节存储在高地址处。简单来说,数据的高字节放在内存的起始位置。
  • 在小端字节序中,低位字节存储在低地址处,高位字节存储在高地址处。也就是说,数据的低字节放在内存的起始位置。

http://www.mrgr.cn/news/77877.html

相关文章:

  • 本地安装YAPI
  • 深入理解索引(一)
  • element dialog 2层弹窗数据同步问题
  • react 使用中注意事项提要
  • 再次讨论下孤注一掷
  • 汽车HiL测试:利用TS-GNSS模拟器掌握硬件性能的仿真艺术
  • 使用猴子补丁对pytorch的分布式接口进行插桩
  • 鸿蒙进阶篇-状态管理之@Prop@Link
  • 机器学习周志华学习笔记-第4章<决策树>
  • Android Framework WMS面试题及参考答案
  • YOLOv11融合[NeurlS2022]递归门控卷积gnconv模块及相关改进思路
  • 深度优先搜索(dfs)题目合集
  • (长期更新)《零基础入门 ArcGIS(ArcMap) 》实验一(下)----空间数据的编辑与处理(超超超详细!!!)
  • Python 爬虫 (1)基础 | 基础操作
  • 「Mac玩转仓颉内测版30」基础篇10 - 区间类型详解
  • springboot配置https,并使用wss
  • logback动态获取nacos配置
  • Spring 中的 ProxyFactory 创建代理对象
  • 学习Servlet (Servlet的实现方式1)
  • 英语写作中“联系、关联”associate correlate 及associated的用法
  • 28.UE5游戏框架,事件分发器,蓝图接口
  • 17. 指针类型和步长概念问题
  • Node相关教程
  • css效果
  • vue面试题——描述一下vue
  • Linux高阶——1123—