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() 网络字节序转换成主机字节序。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/921885.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Tri Mode Ethernet MAC IP核详解

本文对 Vivado 的三速 MAC IP 核&#xff08;Tri Mode Ethernet MAC&#xff0c;TEMAC&#xff09;进行介绍。 在自行实现三速以太网 MAC 控制器时&#xff0c;GMII/RGMII 接口可以通过 IDDR、ODDR 原语实现&#xff0c;然而实际使用中自己实现的模块性能不是很稳定&#xff08…

CENTOS7 升级gcc版本

升级gcc版本 CentOS下升级gcc版本有两个途径&#xff0c;一个是添加其他源进行自动升级&#xff0c;一个是手动编译升级&#xff0c;这里先顺便讲下自动升级的两个办法&#xff1a; a. 添加Fedora源 在 /etc/yum.repos.d 目录中添加文件 FedoraRepo.repo &#xff0c;并输入…

VMware虚拟机(Ubuntu或centOS)共享宿主机网络资源

VMware虚拟机(Ubuntu或centOS)共享宿主机网络资源 由于需要在 Linux 环境下进行一些测试工作&#xff0c;于是决定使用 VMware 虚拟化软件来安装 Ubuntu 24.04 .1操作系统。考虑到测试过程中需要访问 Github &#xff0c;要使用Docker拉去镜像等外部网络资源&#xff0c;因此产…

学习日记_20241123_聚类方法(高斯混合模型)续

前言 提醒&#xff1a; 文章内容为方便作者自己后日复习与查阅而进行的书写与发布&#xff0c;其中引用内容都会使用链接表明出处&#xff08;如有侵权问题&#xff0c;请及时联系&#xff09;。 其中内容多为一次书写&#xff0c;缺少检查与订正&#xff0c;如有问题或其他拓展…

15.C++STL 2(string类的使用,6000字详解)

⭐本篇重点&#xff1a;string类的使用 ⭐本篇代码&#xff1a;c学习/05.string类的学习 橘子真甜/c-learning-of-yzc - 码云 - 开源中国 (gitee.com) 目录 一. C/C字符与string类 二. STL中的string类的使用 2.1 string类常见的构造与赋值 2.2 string对象的数据容量操作 …

神经网络(系统性学习一):入门篇——简介、发展历程、应用领域、基本概念、超参数调优、网络类型分类

相关文章&#xff1a; 神经网络中常用的激活函数 神经网络简介 神经网络&#xff08;Neural Networks&#xff09;是受生物神经系统启发而设计的数学模型&#xff0c;用于模拟人类大脑处理信息的方式。它由大量的节点&#xff08;或称为“神经元”&#xff09;组成&#xff0…

shell 基础知识2 ---条件测试

目录 一、条件测试的基本语法 二、文件测试表达式 三、字符串测试表达式 四、整数测试表达式 五、逻辑操作符 六、实验 为了能够正确处理 Shell 程序运行过程中遇到的各种情况&#xff0c; Linux Shell 提供了一组测试运算符。 通过这些运算符&#xff0c;Shell 程序能够…

数据指标与标签在数据分析中的关系与应用

导读&#xff1a;分享数据指标体系的文章很多&#xff0c;但讲数据标签的文章很少。实际上&#xff0c;标签和指标一样&#xff0c;是数据分析的左膀右臂&#xff0c;两者同样重要。实际上&#xff0c;很多人分析不深入&#xff0c;就是因为缺少对标签的应用。今天系统的讲解下…

Flutter-Web首次加载时添加动画

前言 现在web上线后首次加载会很慢&#xff0c;要5秒以上&#xff0c;并且在加载的过程中界面是白屏。因此想在白屏的时候放一个加载动画 实现步骤 1.找到web/index.html文件 2.添加以下<style>标签内容到<head>标签中 <style>.loading {display: flex;…

51单片机基础 06 串口通信与串口中断

目录 一、串口通信 二、串口协议 三、原理图 四、串口通信配置参数 1、常用的串行口工作方式1 2、数据发送 3、数据接收 4、波特率计算 5、轮询接收 6、中断接收 一、串口通信 串口通信是一种常见的数据传输方式&#xff0c;广泛用于计算机与外部设备或嵌入式系统之间…

【深度学习之回归预测篇】 深度极限学习机DELM多特征回归拟合预测(Matlab源代码)

深度极限学习机 (DELM) 作为一种新型的深度学习算法&#xff0c;凭借其独特的结构和训练方式&#xff0c;在诸多领域展现出优异的性能。本文将重点探讨DELM在多输入单输出 (MISO) 场景下的应用&#xff0c;深入分析其算法原理、性能特点以及未来发展前景。 1、 DELM算法原理及其…

动态规划子数组系列一>最长湍流子数组

1.题目&#xff1a; 解析&#xff1a; 代码&#xff1a; public int maxTurbulenceSize(int[] arr) {int n arr.length;int[] f new int[n];int[] g new int[n];for(int i 0; i < n; i)f[i] g[i] 1;int ret 1;for(int i 1; i < n-1; i,m. l.kmddsfsdafsd){int…

RabbitMQ3:Java客户端快速入门

欢迎来到“雪碧聊技术”CSDN博客&#xff01; 在这里&#xff0c;您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者&#xff0c;还是具有一定经验的开发者&#xff0c;相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导&#xff0c;我将…

【mongodb】社区版8:改变配置bindip和授权

更改配置 sudo systemctl restart mongod (base) root@k8s-master-pfsrv:/home/zhangbin# sudo tail -n 20 /var/log/mongodb/mongod.log 日志感觉是成功了:{"t":{"$date":"2024-11-19T19:57:47.076+08:00"

element dialog 2层弹窗数据同步问题

注意&#xff1a;本帖为公开技术贴&#xff0c;不得用做任何商业用途 element dialog 2层弹窗数据同步问题 如果嵌套dialog&#xff0c;也就是多层dialog嵌套 2个input&#xff0c;key用同样的值 会导致内外2层dialog&#xff0c;用相同key值的input会数据同步 原因如下&a…

C# 属性 学习理解记录

字段和属性 左边字段&#xff0c;右边属性 拓展&#xff0c;属性安全&#xff1a; 1、设置public private 和protected 等&#xff0c;只读&#xff0c;只写&#xff0c; 2、在get set 方法时&#xff0c;验证&#xff0c;异常时抛出错误

如何提取某站 MV 视频中的音乐为 MP3 音频

我们常常会遇到需要将视频中的音频提取出来并转换为 MP3 格式的情况&#xff0c;比如想要单独保存一段视频中的精彩音乐、演讲或旁白。简鹿视频格式转换器就是一款能够轻松实现这一需求的实用软件&#xff0c;它支持 Windows 和 Mac 系统&#xff0c;为不同操作系统的用户提供了…

SQLynx让数据库变得简单!

SQLynx让数据库管理和开发变得更简单&#xff0c;SQLynx是一款旨在简化飞客使用体验的创新型工具&#xff0c;它为数据库管理者、数据库分析师和开发人员提供了一个直观、易用、高效的平台&#xff0c;首先&#xff0c;SQLynx拥有直观友好的用户界面。无论您是新建还是导表&…

stm32f10x_tim.h(函数学习自用)

stm32f10x_tim.h 函数库 void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct); //时基单元配置void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDe…

Centos Stream 9安装Jenkins-2.485 构建自动化项目步骤

官网&#xff1a;https://www.jenkins.io/ 1 下载 环境准备&#xff1a; 版本支持查询&#xff1a;https://pkg.jenkins.io/redhat-stable/ 安装JDK17&#xff1a;https://blog.csdn.net/qq_44870331/article/details/140784297 yum -y install epel-release wget upgradew…