Linux: C语言解析域名

devtools/2024/11/24 8:30:21/

在上一篇博客 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.ppmy.cn/devtools/136508.html

相关文章

Spring Boot OA系统:企业资源规划的新选择

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

【UE5】使用基元数据对材质传参,从而避免新建材质实例

在项目中&#xff0c;经常会遇到这样的需求&#xff1a;多个模型&#xff08;例如 100 个&#xff09;使用相同的材质&#xff0c;但每个模型需要不同的参数设置&#xff0c;比如不同的颜色或随机种子等。 在这种情况下&#xff0c;创建 100 个实例材质不是最佳选择。正确的做…

【Next】中间件

概述 Next.js 的 中间件 (Middleware) 是一种在请求完成之前运行的函数&#xff0c;用于对入站请求进行处理和操作。它可以在路由匹配前执行逻辑&#xff0c;用于身份验证、请求重写、重定向、设置响应头等任务。 使用场景 身份验证&#xff1a;在用户访问页面前检查登录状态…

天润融通携手挚达科技:AI技术重塑客户服务体验

业务爆发式增长&#xff0c;但座席服务却跟不上&#xff0c;怎么办&#xff1f; 智能充电领导者的挚达科技就面临过 这样的问题&#xff0c;让我们来看看如何解决。 2010年以来&#xff0c;国内新能源汽车市场进入高速发展期&#xff0c;作为新能源汽车的重要配件&#xff0c…

Java与Kotlin在鸿蒙中的地位

在当今移动操作系统领域&#xff0c;华为推出的鸿蒙系统&#xff08;HarmonyOS&#xff09;正逐渐崭露头角&#xff0c;成为与Android、iOS并驾齐驱的操作系统之一。对于开发者而言&#xff0c;了解如何为鸿蒙系统开发高质量的应用程序变得至关重要。在这篇文章中&#xff0c;我…

ThreadLocal 和 Caffeine 缓存是两种不同的缓存机制,它们在用途和实现上有明显的区别

ThreadLocal 和 Caffeine 缓存是两种不同的缓存机制&#xff0c;它们在用途和实现上有明显的区别&#xff1a; ThreadLocal 缓存&#xff1a; ThreadLocal 提供了线程局部变量的功能&#xff0c;每个线程可以访问自己的局部变量&#xff0c;而不会与其他线程冲突。ThreadLocal …

项目中排查bug的思路案例

bug描述&#xff1a;调用了删除的接口&#xff0c;执行成功了&#xff0c;也删掉了选中的数据&#xff0c;但是不执行删除后的处理操作&#xff0c;会报一个“系统未知错误&#xff0c;请反馈给管理员” 解决&#xff1a; 成功删掉了数据&#xff0c;但删除后的操作没有执行&a…

SpringMVC-01-回顾MVC

1. 回顾MVC 1.1. 什么是MVC MVC是模型(Model)、视图(View)、控制器(Controller)的简写&#xff0c;是一种软件设计规范。是将业务逻辑、数据、显示分离的方法来组织代码。MVC主要作用是降低了视图与业务逻辑间的双向偶合。MVC不是一种设计模式&#xff0c;MVC是一种架构模式。…