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

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 ID
    unsigned short flags; // DNS flags
    unsigned short qdcount; // Number of questions
    unsigned short ancount; // Number of answers
    unsigned short nscount; // Number of authority records
    unsigned 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 data
    unsigned 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 RDLENGTH
    printf("TYPE: ");
    switch (TYPE) {
        case 1:  // A Record
            printf("A\n");
            break;
        case 5:  // CNAME Record
            printf("CNAME\n");
            break;
        default:
            printf("%d\n", TYPE);  // For other record types, just print the number
            break;
    }

    printf("CLASS: ");
    switch (CLASS) {
        case 1:  // IN (Internet)
            printf("IN\n");
            break;
        default:
            printf("%d\n", CLASS);  // For other classes, just print the number
            break;
    }

    printf("TTL: %d\n", TTL);
    printf("RDLENGTH: %d\n", RDLENGTH);

    // Handle RDATA based on the record type
    if (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 it
        printf("RDATA (CNAME): ");
        unsigned char *cname = answer->rdata;
        // DNS names are in "label" format, so we need to handle them accordingly
        while (*cname != 0) {
            int label_length = *cname;  // Length of the label
            cname++;
            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);   // UDP
    
    if (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);
    else
        server_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.kler.cn/a/413327.html

相关文章:

  • 如何使用MySQL实现多租户架构:设计与实现全解析
  • 新160个crackme - 105-royalaccezzcrackme
  • Linux服务器生成SSH 密钥对与 GitLab 仓库进行交互
  • 学习Java的日子 Day56 数据库连接池,Druid连接池
  • 五种创建k8s的configMap的方式及configmap使用
  • 鸿蒙多线程开发——sendable共享容器
  • 探索光耦:光耦安全标准解读——确保设备隔离与安全的重要规范
  • linux安全管理-系统环境安全
  • Leetcode437. 路径总和 III(HOT100)
  • BERT的中文问答系统38
  • 猎户星空发布MoE大模型,推出AI数据宝AirDS
  • unity中的Horizontal和Vertical介绍
  • 深入解析经典排序算法:原理、实现与优化
  • 富格林:有效追损正确提高出金
  • 部署 DeepSpeed以推理 defog/sqlcoder-70b-alpha 模型
  • Qt Qt::UniqueConnection 底层调用
  • 多目标优化算法——多目标粒子群优化算法(MOPSO)
  • uni-app 蓝牙开发
  • C++设计模式行为模式———策略模式
  • python控制鼠标,键盘,adb
  • 使用SQL按每小时统计数据的方法
  • C#设计模式——抽象工厂模式(重点)
  • Python使用ffmpeg进行本地视频拉流,并使用训练模型识别人脸,并将识别后的模型推流源码
  • frida_hook_libart(简单解释)
  • 介绍SSD硬盘
  • C#里怎么样使用LINQ的let关键字实现查询?