使用C/C++实现DNS协议栈

使用C/C++实现DNS协议栈

DNS,全称域名系统(Domain Name System),是用于将域名转换为IP地址的分布式数据库系统。实现一个完整的DNS协议栈是一个相对复杂的任务,但本文将为您提供一个简化的概述和实际的案例,以帮助您入门。
在这里插入图片描述

1. 基础知识

在深入了解DNS协议栈的实现之前,您需要了解以下基础知识:

  • 报文格式:DNS消息有一个固定的头部和四个可能的段:问题、回答、权威名称服务器和额外信息。
  • 查询类型:例如A记录(IPv4地址)、AAAA记录(IPv6地址)、MX记录(邮件交换)等。
  • UDP和TCP:DNS主要使用UDP进行通信,但如果响应超过512字节,可能会使用TCP。

2. 实现步骤

以下是使用C/C++实现DNS协议栈的基本步骤:

  1. 创建Socket:首先,您需要创建一个UDP socket。在Linux上,可以使用socket()函数。
  2. 设置Socket:设置socket为非阻塞模式可能是一个好主意,这样您可以设置超时并避免长时间等待。
  3. 发送查询:根据DNS报文格式构建查询消息,并通过socket发送。
  4. 接收响应:接收从DNS服务器返回的响应,并解析该响应以获取所需的信息。
  5. 解析响应:根据DNS报文格式解析响应,提取所需的数据。
  6. 关闭Socket:完成查询后,关闭socket。

3. 实际案例:简单的DNS查询工具

下面是一个简单的示例,展示如何使用C语言和Linux套接字API发送一个A记录的DNS查询:

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>

#define DNS_SERVER "8.8.8.8"  // Google Public DNS
#define DNS_PORT 53
#define QUERY_TYPE 0x01       // A Record
#define CLASS 0x01            // IN

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Usage: %s <domain>\n", argv[0]);
        return 1;
    }

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket");
        return 1;
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(DNS_PORT);
    if (inet_pton(AF_INET, DNS_SERVER, &server_addr.sin_addr) <= 0) {
        perror("inet_pton");
        close(sockfd);
        return 1;
    }

    // 构建查询消息 (这里简化为只查询A记录)
    char query[512]; // 简化的示例,不考虑名字的长度等细节
    memset(query, 0, sizeof(query));
    query[0] = 0x10; // 标准查询, 无递归
    query[2] = QUERY_TYPE; // 查询类型: A Record
    query[3] = CLASS; // 查询类别: IN
    strcpy(query + 12, argv[1]); // 将域名复制到查询消息中 (这里简化了域名编码的细节)
    int query_len = strlen(argv[1]) + 12; // 简化的长度计算,实际情况会更复杂

    // 发送查询消息到DNS服务器
    if (sendto(sockfd, query, query_len, 0, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("sendto");
        close(sockfd);
        return 1;
    }

    // 接收响应消息 (简化为只接收一次)
    char response[512]; // 通常响应不会超过512字节,但实际情况可能需要处理更大的响应或分片的情况
    int len = recvfrom(sockfd, response, sizeof(response), 0, NULL, NULL);
    if (len < 0) {
        perror("recvfrom");
        close(sockfd);
        return 1;
    }
    response[len] = '\0'; // 确保字符串以null结尾,方便打印和处理

    printf("Response from DNS server: %s\n", response); // 这里只是简单地打印响应,实际上需要解析响应以获取所需的信息。

    close(sockfd); // 关闭socket并结束程序。在更复杂的程序中可能需要进一步处理错误和异常情况。
    return 0; // 程序正常结束。在更复杂的程序中可能需要返回更详细的状态信息或处理结果。
}

更详细的代码

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// DNS报文头部结构
struct dns_header {
    unsigned short id;            // 查询ID
    unsigned char rd : 1;         // 递归期望
    unsigned char tc : 1;         // 截断标志
    unsigned char aa : 1;         // 授权回答
    unsigned char opcode : 4;     // 操作码
    unsigned char qr : 1;         // 查询/响应标志
    unsigned char rcode : 4;      // 响应代码
    unsigned char z : 3;          // 未使用
    unsigned short qdcount;       // 问题数
    unsigned short ancount;       // 回答数
    unsigned short nscount;       // 权威名称服务器数
    unsigned short arcount;       // 额外记录数
};

// 将域名转换为DNS消息格式
void encodeDomainName(const std::string& domain, char* buffer, int& index) {
    const char* labelStart = domain.c_str();
    const char* labelEnd = strchr(labelStart, '.');
    while (labelEnd) {
        *buffer++ = labelEnd - labelStart;
        memcpy(buffer, labelStart, labelEnd - labelStart);
        buffer += labelEnd - labelStart;
        labelStart = labelEnd + 1;
        labelEnd = strchr(labelStart, '.');
    }
    *buffer++ = strlen(labelStart);
    strcpy(buffer, labelStart);
    index += strlen(labelStart) + 1;
    *buffer++ = 0x00; // 空标签终止
    index++;
}

// 构建并发送DNS查询报文
int sendDnsQuery(const std::string& domain, const std::string& dnsServer, int dnsPort) {
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket");
        return -1;
    }

    struct sockaddr_in serverAddr;
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(dnsPort);
    if (inet_pton(AF_INET, dnsServer.c_str(), &serverAddr.sin_addr) <= 0) {
        perror("inet_pton");
        close(sockfd);
        return -1;
    }

    dns_header header;
    memset(&header, 0, sizeof(header));
    header.id = htons(12345); // 查询ID,可以随机生成或递增
    header.rd = 1; // 期望递归查询
    header.qr = 0; // 这是一个查询报文
    header.opcode = 0; // 标准查询
    header.qdcount = htons(1); // 查询一个问题

    char queryBuffer[512]; // 查询缓冲区,通常足够大以容纳一个查询报文,但可能需要根据实际情况调整大小。
    memset(queryBuffer, 0, sizeof(queryBuffer));
    memcpy(queryBuffer, &header, sizeof(header)); // 将头部复制到缓冲区中。
    int index = sizeof(dns_header); // 当前缓冲区的索引位置。用于后续的数据添加。
    encodeDomainName(domain, queryBuffer + index, index); // 将域名编码为DNS格式并添加到缓冲区中。之后需要增加索引位置。  

// TODO: 这里缺少了域名编码的完整逻辑,应该遍历域名中的每个标签,并正确编码它们。上面的函数只是一个占位符。
// 添加查询类型和类到缓冲区中。这里以A记录(IPv4地址)和IN类为例。 queryBuffer[index++] = 0x00;
// 查询类型:A记录 queryBuffer[index++] = QUERY_TYPE; queryBuffer[index++] = 0x00;
// 查询类:IN queryBuffer[index++] = CLASS;
// TODO: 这里缺少了将查询报文发送到DNS服务器的完整逻辑。应该使用sendto()函数将报文发送到服务器。
close(sockfd); return 0; }
// 主函数,演示如何发送一个DNS查询
int main()
{
sendDnsQuery(“www.example.com”, DNS_SERVER, DNS_PORT); return 0;
}```

上面的代码演示了如何构建一个简单的DNS查询报文并将其发送到DNS服务器。请注意,这个示例是简化的,并且缺少了一些关键部分(如完整的域名编码逻辑和发送报文的逻辑)。在实际应用中,您需要根据DNS协议的规范来完善这些部分,并处理各种可能的错误和异常情况。 此外,这个示例只涵盖了DNS查询报文的编码和发送部分。要完整实现DNS协议栈,您还需要实现报文的解码、响应的处理以及其他DNS记录类型(如AAAA、MX、CNAME等)的支持。这需要深入了解DNS协议的具体细节和规范。

请注意,这个示例非常简化,没有处理很多实际情况和细节(如域名编码、响应解析、错误处理等)。完整的DNS协议栈实现会涉及更多的复杂性和细节处理。但这个简单的例子可以作为您开始的起点,帮助您了解基本步骤和概念。

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

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

相关文章

Markdown编辑器常用颜色背景指南(附颜色与代码展示,cv即可用)

目录 一.字体颜色1)常用html代码2)通过【颜色代码表】直接改写 二.字体背景颜色1)常用html代码 一.字体颜色 1)常用html代码 html代码 <font colorred> text </font> 常用颜色及其对应的十六进制和颜色名: 红色 p 红色 #FF0000 red <font color#FF0000> t…

一个 tomcat 下如何部署多个项目?附详细步骤

一个tomcat下如何部署多个项目&#xff1f;Linux跟windows系统下的步骤都差不多&#xff0c;以下linux系统下部署为例。windows系统下部署同理。 1 不修改端口&#xff0c;部署多个项目 清楚tomcat目录结构的应该都知道&#xff0c;项目包是放在webapps目录下的&#xff0c;那…

运维开发实践 - 服务网关 - apisix部署

1. Apache Apisix Apache Apisix 是一个动态&#xff0c;实时&#xff0c;高性能的云原生API网关&#xff0c;提供负载均衡&#xff0c;动态上游&#xff0c;灰度发布&#xff0c;服务熔断&#xff0c;身份认证&#xff0c;可观测性等丰富的流量管理功能&#xff1b; 2. 如…

【STM32】STM32学习笔记-对射式红外传感器计次 旋转编码器计次(12)

00. 目录 文章目录 00. 目录01. NVIC相关函数1.1 NVIC_PriorityGroupConfig函数1.2 NVIC_PriorityGroup类型1.3 NVIC_Init函数1.4 NVIC_InitTypeDef类型 02. 外部中断相关API2.1 GPIO_EXTILineConfig2.2 EXTI_Init2.3 EXTI_GetITStatus2.4 EXTI_ClearITPendingBit2.5 中断回调函…

Axure的安装及界面基本功能介绍

目录 一. Axure概述 二. Axure安装 2.1 安装包下载 2.2 安装步骤 三. Axure功能介绍​ 3.1 工具栏介绍 3.1.1 复制&#xff0c;剪切及粘贴 3.1.2 选择模式和连接 3.1.3 插入形状 3.1.4 点&#xff08;编辑控点&#xff09; 3.1.5 置顶和置底 3.1.6 组合和取消组合 …

gitlab 通过svn hook 触发

jenkins 起一个item 配置&#xff1a; 我选的自由风格的 源码管理配置 先选subversion 就是svn类型 url 设置project 的路径&#xff0c; 注意是工程&#xff0c;不是svn 顶层 添加一个账户来进行pull 等操作 选择添加的账号 构建触发器&#xff1a; &#xff0c;重要的是要自…

Mr. Cappuccino的第65杯咖啡——MacOS安装Docker

MacOS安装Docker 下载Docker安装Docker查看Docker相关信息镜像加速 下载Docker Docker官网 Docker文档中心 Docker桌面版下载地址 安装Docker 查看Docker相关信息 docker --versiondocker info镜像加速 阿里云镜像加速器 "registry-mirrors": ["https://gq8…

SpringData JPA 整合Springboot

1.导入依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0…

基于C/C++的非系统库自定义读写ini配置

INI文件由节、键、值组成。 节 [section] 参数 &#xff08;键值&#xff09; namevalue 这里将常用的操作方式封装成了一个dll供外部使用 // 下列 ifdef 块是创建使从 DLL 导出更简单的 // 宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的 LIBCFG_EXPORTS // 符号…

【计算机视觉--解耦视频分割跟踪任何物体】

UIUC&Adobe开源|无需监督&#xff0c;使用解耦视频分割跟踪任何物体&#xff01;视频分割的训练数据往往昂贵且需要大量的标注工作。这限制了将端到端算法扩展到新的视频分割任务&#xff0c;特别是在大词汇量的情况下。为了在不为每个个别任务训练视频数据的情况下实现“跟…

创建型模式之工厂模式

​ 本质&#xff1a; 实例化对象不直接使用new&#xff0c;而是用工厂代替 工厂模式分为&#xff1a; 简单工厂模式&#xff1a;用来生产同一等级结构中的任意产品&#xff08;增加新产品需要修改已有代码&#xff09;工厂方法模式&#xff1a;用来生产同一等级结构中的固定产…

Learning Semantic-Aware Knowledge Guidance forLow-Light Image Enhancement

微光图像增强&#xff08;LLIE&#xff09;研究如何提高照明并生成正常光图像。现有的大多数方法都是通过全局和统一的方式来改善低光图像&#xff0c;而不考虑不同区域的语义信息。如果没有语义先验&#xff0c;网络可能很容易偏离区域的原始颜色。为了解决这个问题&#xff0…

[原创][R语言]股票分析实战[2]:周级别涨幅趋势的相关性

[简介] 常用网名: 猪头三 出生日期: 1981.XX.XX QQ联系: 643439947 个人网站: 80x86汇编小站 https://www.x86asm.org 编程生涯: 2001年~至今[共22年] 职业生涯: 20年 开发语言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 开发工具: Visual Studio、D…

linux 应用开发笔记---【信号:基础】

1.基本概念 信号是发生事件时对进程的通知机制&#xff0c;也可以称为软件中断 信号的目的是用来通信的 1.硬件发生异常&#xff0c;将错误信息通知给内核&#xff0c;然后内核将相关的信号给相关的进程 2.在终端输入特殊字符产生特殊信号 3.进程调用kill()将任意信号发送…

springboot使用EasyExcel导出数据

springboot使用EasyExcel导出数据 简介&#xff1a;本文主要描述使用EasyExcel导出数据的简单流程&#xff0c;事实上企业需求一般都比较简单&#xff0c;就是表单数据输出到Excel即可&#xff0c;如果数据量大的话&#xff0c;为了避免占用内存过高或者OOM&#xff0c;使用多…

DeciLM-7B:突破极限,高效率、高精准度的70亿参数AI模型

引言 在人工智能领域&#xff0c;语言模型的发展速度令人瞩目。Deci团队最近推出了一款具有革命性意义的语言模型——DeciLM-7B。这款模型在速度和精确度上都实现了显著的突破&#xff0c;以其70亿参数的规模&#xff0c;在语言模型的竞争中脱颖而出。 Huggingface模型下载&am…

Oracle EBS PAC“定期成本分配处理程序”报错:30004不存在为成本类型、成本组和法人主体定义的帐户

Oracle EBS版本&#xff1a; RDBMS : 12.1.0.2.0 Oracle Applications : 12.2.6 问题症状&#xff1a; 中文环境&#xff1a; 30004不存在为成本类型、成本组和法人主体定义的帐户。 CSTPALPC.dyn_proc_call : Error Calling Package 30004不存在为成本类型、成本组和法人主…

牛客网 DP34 【模板】前缀和(优质解法)

代码&#xff1a; import java.util.Scanner;// 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextInt()) { // 注…

【数据分享】2019-2023年我国区县逐年二手房房价数据(Excel/Shp格式)

房价是一个区域发展程度的重要体现&#xff0c;一个区域的房价越高通常代表这个区域越发达&#xff0c;对于人口的吸引力越大&#xff01;因此&#xff0c;房价数据是我们在各项城市研究中都非常常用的数据&#xff01;之前我们分享了2019—2023年我国区县逐月的二手房房价数据…

【OpenGL/WebGL】Shader中如何获取摄像机视口的宽高

一、需求背景 在有些需求中&#xff0c;物体的大小是随着摄像机的视口的大小而变化的。如下图中&#xff0c;蓝色小方块&#xff0c;随着不断放大&#xff0c;其大小有个最大值&#xff0c;并不会无限放大。 这种实现的原理是在Shader中&#xff0c;不断根据摄像机近平面尺寸大…