自上而下的理解网络(1)——DNS篇

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 现代生活中,网络可谓是无处不在,购物需要网络,付款需要网络,各种生活缴费需要网络,在各行各业的工作中,更是离不开网络。说到底,网络的作用无非是支持计算机间进行数据交换。世界各地有着不计其数的网络设备,这些网络设备是如何有序正常的进行数据交流的呢?网络以及各种协议的工作原理又是怎样的呢?本系列博客,我们将尝试自上而下的对网路的工作原理进行介绍,从应用层开始,逐层向下,详细的帮助你理解网络的核心工作原理。当然,网络协议多如牛毛,在网络分层中每一层的知识也是非常浩渺,希望这些博客可以起到抛砖引玉的作用,能够使你对于天天使用的互联网网络在宏观上有认识,在微观上也有了解。

自上而下的理解网络(1)——DNS篇

一.引言

现代生活中,网络可谓是无处不在,购物需要网络,付款需要网络,各种生活缴费需要网络,在各行各业的工作中,更是离不开网络。说到底,网络的作用无非是支持计算机间进行数据交换。世界各地有着不计其数的网络设备,这些网络设备是如何有序正常的进行数据交流的呢?网络以及各种协议的工作原理又是怎样的呢?本系列博客,我们将尝试自上而下的对网路的工作原理进行介绍,从应用层开始,逐层向下,详细的帮助你理解网络的核心工作原理。当然,网络协议多如牛毛,在网络分层中每一层的知识也是非常浩渺,希望这些博客可以起到抛砖引玉的作用,能够使你对于天天使用的互联网网络在宏观上有认识,在微观上也有了解。

二.访问网站的第一步是什么?

说到网络,对于普通用户来说,使用最多的可能就是浏览各种网站了,虽然现在移动设备上的App基本代替了传统的PC应用和网站,但是这些App里提供的数据本质上网站中提供的数据并无不同,使用的网络技术并无不同。

我们知道,不论是访问网站还是App内进行接口请求,这些数据都是存储在“服务器”这种特殊的远程设备上的,要向服务器获取数据,首先我们需要找到服务器的位置,这很好理解,只有找到它,我们才能和它产生数据交流。互联网无论多大,本质上依然是通过电缆、光纤或各种无线设备这类连接介质连接在一起的,如果一台设备没有硬件上连接入互联网,那么说破天我们也无法和它产生数据交互。要找到一台互联网设备,实际上是通过其物理Mac地址来找到的,这就像现实中的门牌号一样,每家的门牌号都不同,说到这,我们要再老生常谈一下,抛出网络分层模型给你看:

关于这个网络分层模型,它在我们后面博客中的出境还少不了,现在你可以先不用管它,你只需要先知道物理层是负责设备物理媒介相关的协议,数据链路层通过硬件的Mac地址找到具体要网络设备,网络层通过IP协议来封装真实的Mac地址,传输层是对网络层的一种封装,TCP,UDP等传输协议在这一层工作,而最上层的应用层就是我们常说的网络应用协议,如DNS,HTTP,HTTPS和FPT协议工作在这一层。

关于网络分层模型,我们先把多说了,我们的宗旨是自上而下的理解网络,那么还是回到第一步来。我们在访问网站时,都会现在浏览器输入网站的地址,这通常是一个域名,例如我要访问自己的技术博客网站,我会在浏览器输入如下的地址:

https://huishao.cc/

huishao.cc就是一个域名,首先只通过域名我们是找不到要访问的对方服务器的,这就好像现实中我要去小王家,可以我只知道小王的名字“王某某”是无法找到他的家的,我需要有一个住址簿,告诉我小王究竟住在哪了,这样我才能找到他。当然,此住址可能也不是真正的物理位置,可能是一个社区,比如小王住在“光明社区”,具体光明社区在哪,我们可以再通过查看地图获取。对应到互联网中,域名就是一个名字,它方便我们对网站进行记忆,IP地址则是要访问的对方在逻辑上的地址,这方便互联网的网络管理,最终的硬件地址则是真正的对方位置。IP地址到硬件地址的映射,等我们讨论到了再细聊,本篇博客我们就说域名到IP地址映射这一过程。

三. DNS服务器

现在你应该已经明确,要通过域名找到某个设备,第一步是先得到此域名对应的IP地址,那么此IP地址是怎么得到的呢?首先,一定有一个地方维护了域名与IP地址的映射关系,如果你有过建站的经历,那么你一定进行过域名绑定操作,一个网站建成后,理论上就已经可以使用IP的方式来进行访问,但是为了易记和动态变动IP,通常会对其进行域名绑定。由域名获取到IP的这一过程,我们称之为域名解析。

域名解析是一种服务,提供域名解析服务的服务器即是DNS服务器,下图可以很形象的表示域名服务器的工作方式:

可以发现,映射表中记录了域名与IP间的映射关系,在实际的应用中,上图中描述的场景看似可行,实际却并非如此,世界上的域名与IP总数是一个非常庞大的数字,由一台服务器来维护所有域名IP信息几乎不可能,而且对于域名解析服务,请求量是巨大的,会有大量的用户频繁的进行域名解析请求,单服务器明显是不能满足需求的。因此,实际生产环境中的DNS解析是采用层层递进,多级缓存,递归查询的方式进行的。再看下图:

上图看似复杂,实际上只是描述了三个关键词:层层递进,多级缓存,递归查询。

四.DNS解析过程

下面我们来解释域名要解析成正确的IP地址,要经过的几个重要过程。

1. 本机hosts文件

本机hosts文件是优先级最高的域名IP映射表,对于Mac操作系统,这个文件在根目录的etc文件夹下,我们可以直接将域名与对应的IP写在这个文件中,在进行域名解析时,首先会从这个文件中找。广播IP和本机IP对应的域名实际上就定义在这里,如下:

127.0.0.1       localhost
255.255.255.255 broadcasthost

你也可以在其中新增任意映射,例如将huishao.cc的域名映射到127.0.0.1的本机IP,保存后,在浏览器再输入huishao.cc,你将无法再访问到珲少的博客网站,如下图所示:

更多时候,hosts的正确用法是开发应用程序时,测试环境和正式环境可以将域名配置到不同的IP,这样无需应用程序代码中做逻辑,只需要切换hosts文件即可实现环境的切换。

2. 本机应用缓存

本机应用缓存是多级缓存中的第一级,例如当我们在浏览器中访问过某个域名后,其解析的结果会被浏览器缓存下来,当我们再次访问这个域名时,其首先会检查浏览器缓存,如果缓存能够命中此域名,则直接使用,缓存的有效时间会受TTL配置影响(我们后面会介绍)。

3. 本机系统缓存

与本机应用缓存类似,操作系统中也会有一份域名解析的缓存,如果本机应用缓存中没有命中,会从操作系统缓存中检查是否之前有过此域名的解析记录。如果能够命中则会直接使用。

4. 路由器域名解析缓存

如果本机系统缓存依然没有命中,而你的设备又是通过路由器接入的公网,此时你的域名解析服务很大可能是路由器提供的,可以打开网络设置的DNS一栏,观察DNS服务器的地址,如果是192.168.x.x类型内网地址,则说明是由路由器来完成DNS解析了。如下图所示:

路由器内,实际上也会缓存一张DNS解析表,会从其中寻找是否有可以命中的缓存,如果存在并且未过期,则直接使用。有时候,你会发现电脑可以直接使用IP访问网站但是无法使用域名进行访问,很大可能是路由器的DNS服务出问题了,最简单的解决方式就是将配置的DNS服务器IP地址改成公共的。

5. 访问本地域名服务器

如果以上的缓存都没有命中,那么逻辑上我们就需要通过外网的DNS服务来进行解析了,首先本地服务器(LDNS)来解析域名,这里的本地服务器是指城市或区域的DNS服务器,一般就有运营商部署在当地,距离近,性能好,并且也有缓存机制,几乎可以覆盖大多数的域名解析请求。

6. 转发与递归

如果你访问的域名比较冷门,本地服务器依然无法解析,则会进行转发,将此请求转发到更高级的运营商DNS服务器或者根DNS服务器,根DNS服务器会根据域名来返回顶级的域名服务器地址,本地服务器可以继续向顶级域名服务器请求解析。如此递归进行,直到解析成功,再将IP地址依次返回到我们的设备,并逐层做缓存,以便我们下次访问时可以快速得到响应。

上面过程中,我们有提到根域名服务器,其是最高级别的域名服务器,它负责返回顶级域名服务器,目前全球有13个根域名服务器站。顶级域名服务器用来针对某个顶级域名进行解析,例如.com顶级域名,.edu顶级域名,.cc顶级域名和.cn顶级域名等。顶级域名服务器在解析时会将查询到的主域名服务器返回。主域名服务器负责某个区域的域名解析,同样,主域名服务器会配套辅助域名服务器进行备份与分担负载。

五.DNS协议

前面说了这么多,都是宏观上的认识。现在,我们要讨论一些更深入的东西了。虽然对于DNS是干什么的,解析的过程是怎样的我们有了一些了解。但是DNS协议究竟是怎么操作的呢?IP数据是怎么得到的?我们可以手动来进行DNS解析么?要了解这些问题,首先需要对DNS协议本身做个了解。

DNS协议是工作在应用层的一种协议,全称Domain Name System。DNS协议是基于UDP之上实现的,前面说过UDP是工作在传输层的一种网络协议,等我们说到它的时候再深入探讨。现在你只需要知道,基于UDP任何人都可以实现一个DNS解析服务。DNS解析分为两步,首先需要客户端向服务器发送一个DNS请求报文,服务器收到报文,解析完成后再返回一个DNS报文给客户端,此报文中就包含解析的数据。

DNS协议规定其请求报文与响应报文的结构是一致的,都包含Header,Question,Answer,Authority,Additional这5个部分。

1. Header部分

Header部分的长度是一定的,固定为12个字节。DNS协议文档中有一张图,很好的描述了Header的数据结构:

ID:ID占了两个字节,它是一个标识符,由客户端请求的时候填充,DNS服务器解析后,会将此ID返回,用来让客户端将响应与请求对应起来。

配置字段:上图中第2行的都是配置字段,其占了两个字节。

QR占1为,设置为0表示当前是DNS请求报文,设置为1表示当前为DNS响应报文。

Opcode占4位,此值由请求报文设置,并且被复制到响应报文返回。其用来设置查询的类型,设置为0表示标准查询,即由域名解析出IP,设置为1表示反向查询,即由IP反查出域名,设置为2用来查询服务器的状态,3-15为保留字段,以待后续使用。

AA字段占1位,只在返回的响应报文中有,0表示返回数据的服务器不是权威服务器,1表示返回数据的服务器是权威服务器。需要注意,返回的响应报文中可能有多个应答,此字段表明的是第一个应答的服务器类型。

TC字段占1位,表示此报文是否由于数据的传输大小而被截断,当此字段的为1时,数据不可信。

RD字段占1位,该值需要在请求报文中设置,响应报文会直接复制该值。此值表示是否希望服务器进行递归查询。

RA字段占1位,其在响应报文中设置,表示服务端是否支持递归查询。

Z字段占3位,是保留字段。

rcode字段占4位,是响应报文的响应码,0表示没有错误;1表示请求格式有误,服务端无法解析;2表示服务器出错;3表示请求的域名不存在;4表示服务器不支持这类请求;5表示服务器拒绝此次请求;6-15是保留参数。

QDCOUNT:占16位,表明Question部分包含的实例个数,是无符号数。

ANCOUNT:占16位,表明Answer部分包含的回答个数,是无符号数。

NSCOUNT:占16位,表明Authority部分包含的授权服务器数量,是无符号整数。

ARCOUNT:占16位,表明Additional部分中包含的资源记录数量,是无符号整数。

2. Question部分

这个部分用来定义查询的问题,问题的个数在QDCOUNT指明,通常只会携带一个问题。每个问题的格式定义如下:

QNAME:此部分字节数不定,描述要查询的域名。在解析的时候,这部分以0x00结尾。需要注意,域名通常由符号“.”进行分割,每段的长度不定,QNAME每段的开头会先指明此段的长度,以huishao.cc域名为例,其构造出的QNAME部分如下:

0x07 0x68 0x75 0x69 0x73 0x68 0x61 0x6f 0x02 0x63 0x63 0x00

其中最后一个字节0x00标记了QNAME部分的结束,0x07表示第一段的长度为7个字节,即0x68 0x75 0x69 0x73 0x68 0x61 0x6f是第一段,通过查询ascii码对照表可知,这段数据就是huishao,同理,之后的一个字节为0x02,表示第二段的长度为2个字节,0x63对应ascii表中的字母c,最终可以解析为huishao.cc

QTYPE:占两个字节,对应查询的类型,定义如下:

Type:意义 对应的值
A:iPv4主机地址 1
NS:权威域名服务器 2
MD:邮箱地址(弃用,使用MX) 3
MF:转发邮箱(弃用,使用MX) 4
CNAME:规范的别名 5
SOA:标记权威区域开始 6
MB:邮箱域名 7
MG:邮箱成员 8
MR:邮箱重命名域名 9
NULL:空的类型 10
WKS:服务描述 11
PTR:域名指针 12
HINFO:主机信息 13
MINFO:邮箱或者邮件列表信息 14
MX:邮件交换 15
TXT:字符串 16
AAAA: IPv6域名 28

上面列举的查询类型中,有两个我们需要额外关注,A和CNAME,A类型即是我们查询域名IP所要使用的,CNAME别名技术也很常用,后面会介绍。

QCLASS:占两个字节,表明查询的类别,定义如下:

CLASS:意义 对应的值
IN:Internet查询 1
CS:弃用,RFC查询 2
CH:the CHOAS class 3
HS:Hesiod 4

进行DNS解析时,只需要设置成IN类即可。

3. Answer部分

这部分是响应的返回数据,可能包含多条资源记录,其格式如下:

NAME:此记录所属的域名,长度不定,需要注意,这一部分存放的可能是真正的域名(格式和QNAME一致),也可能是指针,指向真正存放域名的字节位置,甚至可以是一部分是域名,一部分是指针。这样做的好处是可以节省响应报文的数据空间,当检查到某个字节的高两位为11时,则此字节及之后一个字节就是一个指针。例如对于huishao.cc域名的解析,其响应的完整的DNS报文如下(16进制):

b3 a4 81 80 00 01 00 01 00 00 00 00 07 68 75 69
73 68 61 6f 02 63 63 00 00 01 00 01 c0 0c 00 01
00 01 00 00 02 58 00 04 b9 c7 6d 99

其中开头的12个字节为Header部,随后的16个字节为Question部,后面的即为Answer部,Answer部分开头的c0字节高两位为11,表明其是一个指针,占两个字节,c0,0c两个字节将前两位的1去掉后为十进制数12,表明NAME的真实值在第12个字节处开始,即复用了QNAME的数据。

TYPE:占两个字节,与QTYPE定义一致。

CLASS:占两个字节,与QCLASS定义一致。

TTL:占4个字节,此字段非常重要,标记了缓存的有效时长,单位是秒。顺便分析一下上面的数据,此DNS解析数据的缓存有效期为0x0258,即600秒,10分钟。

RDLENGTH:占两个字节,表明RDATA字段的字节数。

RDATA:真正的解析数据,与TYPE有关,如果是IPv4域名解析,此处为解析的结果。

4. Authority,Additional

这两部分的数据结构与Answer部分完全一致,解析方式也完全一致。

六.纸上得来终觉浅,绝知此事要躬行

通过前面的介绍,DNS协议的工作原理应该是明了了,如果需要更深入的了解细节,可以阅读其官方的文档:

https://datatracker.ietf.org/doc/html/rfc1035

当然,如果你还是感觉云里雾里也没有关系,我们通过实践来验证理论。

1.抓个活物来看看

Wireshark是一个网络封包分析软件,能够截取网络封包,对于网络传输的数据包进行分析十分方便。我们打开此软件后,找一个域名进行访问,即可抓取到对应的DNS数据包,以huishao.cc为例,如下图所示:

可以看到,Wireshark可以分析出此次网络交互的时间,发起方IP,目标方IP,协议类型,数据长度和相信信息。在上面的示例中,第一条记录是DNS请求报文,第二条记录是DNS响应报文。我们先看看DNS请求报文的数据:

可以看到,Wireshark将每一层网络协议都分析了出来,我们先只关注最上层的Domian Name System部分,这部分的十六进制数据是上图中选中的部分。可以发现其和我们上面介绍的协议格式是一一对应的。在看响应报文:

数据的格式也是完全对应的,理论诚不欺我啊。

2. 手动实现DNS解析

下面,我们可以以huishao.cc域名为例,手动使用UDP协议来试一试发送DNS请求以及对请求到的数据进行解析。首先先看完整的测试代码:

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

// 定义NDS服务器的地址
char *DNSServer = "192.168.1.1";

// DNS报文中查询区域的查询类型
#define A 1
#define CNAME 5

/*
**DNS报文首部
**这里使用了位域
*/
struct DNS_HEADER {
    // 2字节
    unsigned short ID;
    
    // 需要注意,对于结构体中的位域 数据是从低字节开始填充的
    // 1字节
    unsigned char RD :1;
    unsigned char TC :1;
    unsigned char AA :1;
    unsigned char Opcode :4;
    unsigned char QR :1;
    
    // 1字节
    unsigned char RCODE :4;
    unsigned char Z :3;
    unsigned char RA :1;
    
    // 2字节
    unsigned short QCOUNT;
    // 2字节
    unsigned short ANCOUNT;
    // 2字节
    unsigned short NSCOUNT;
    // 2字节
    unsigned short ARCOUNT;
};

/*
**DNS报文中查询问题区域  4个字节
*/
struct QUESTION {
    unsigned short QTYPE;//查询类型
    unsigned short QCLASS;//查询类
};
// 请求部分的结构
typedef struct {
    unsigned char *QNAME;
    struct QUESTION *question;
} QUERY;

/*
**DNS报文中回答区域的常量字段  10个字节
*/
// 需要注意,因为此结构体中有short和int类型,我们需要将其设置为1字节对齐
#pragma pack(1)
struct R_DATA {
    unsigned short TYPE; //表示资源记录的类型
    unsigned short CLASS; //类
    unsigned int TTL; //表示资源记录可以缓存的时间
    unsigned short RDLENGTH; //数据长度
};
#pragma pack()
/*
**DNS报文中回答区域的资源数据字段
*/
struct RES_RECORD {
    unsigned char *NAME;//资源记录包含的域名
    struct R_DATA *resource;//资源数据
    unsigned char *rdata;
};

// DNS解析方法
void DNS(unsigned char*);
// 域名转换方法
int ChangetoDnsNameFormat(unsigned char*, unsigned char*);

/*
**实现DNS查询功能
*/
void DNS(unsigned char *host) {
    
    // UDP目标地址
    struct sockaddr_in dest;
    // DNS请求的数据结构
    struct DNS_HEADER dns = {};

    printf("\n所需解析域名:%s\n", host);
    
    //建立分配UDP套结字
    int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    //IPv4
    dest.sin_family = AF_INET;
    //53号端口 DNS服务器用的是53号端口
    dest.sin_port = htons(53);
    // 设置IP
    dest.sin_addr.s_addr = inet_addr(DNSServer);//DNS服务器IP

    /*设置DNS报文首部*/
    dns.ID = (unsigned short) htons(getpid());//id设为进程标识符
    dns.QR = 0; //查询
    dns.Opcode = 0; //标准查询
    dns.AA = 0; //不授权回答
    dns.TC = 0; //不可截断
    dns.RD = 1; //期望递归
    dns.QCOUNT = htons(1); //1个问题
    // 不需要的字段置为0
    dns.RA = 0;
    dns.Z = 0;
    dns.RCODE = 0;
    dns.ANCOUNT = 0;
    dns.NSCOUNT = 0;
    dns.ARCOUNT = 0;

    // 进行查询的域名处理 先给100个字节大小
    unsigned char *qname = malloc(100);
    // 转换后会将长度返回
    int nameLength = ChangetoDnsNameFormat(qname, host);//修改域名格式
    // 请求结构
    QUERY question = {};
    question.QNAME = qname;
    struct QUESTION qinfo = {};
    qinfo.QTYPE = htons(A); //查询类型为A
    qinfo.QCLASS = htons(1); //查询类为1
    question.question = &qinfo;
    // 定义要发送的UDP数据 先给65536个字节
    unsigned char buf[65536];
    // 复制DNS头部数据到buf
    memcpy(buf, &dns, sizeof(dns));
    // 移动复制的指针
    unsigned char *point = buf + sizeof(dns);
    // 复制请求的域名到buf
    memcpy(point, question.QNAME, nameLength);
    // 移动复制的指针
    point = point + nameLength;
    // 复制要解析的域名到buf
    memcpy(point, question.question, sizeof(*question.question));
    // buf的总长度
    int length = sizeof(dns) + nameLength + sizeof(*question.question);

    //向DNS服务器发送DNS请求报文
    printf("\n\n发送报文中...");
    if (sendto(s, (char*) buf, length, 0, (struct sockaddr*) &dest,sizeof(dest)) < 0)
    {
        perror("发送失败!");
    }
    printf("发送成功!\n");

    // 从DNS服务器接受DNS响应报文
    unsigned char recvBuf[65536];
    int i = sizeof dest;
    printf("接收报文中...\n");
    recvfrom(s, (char*) recvBuf, 65536, 0, (struct sockaddr*) &dest,(socklen_t*) &i);
    if (length < 0) {
        perror("接收失败!");
    }
    printf("接收成功!\n");
    // 将接收到的DNS数据头部解析到结构体
    struct DNS_HEADER recvDNS = *((struct DNS_HEADER *)recvBuf);

    printf("\n\n响应报文包含: ");
    printf("\n %d个问题", ntohs(recvDNS.QCOUNT));
    printf("\n %d个回答", ntohs(recvDNS.ANCOUNT));
    printf("\n %d个授权服务", ntohs(recvDNS.NSCOUNT));
    printf("\n %d个附加记录\n\n", ntohs(recvDNS.ARCOUNT));
    
    // 头部,域名部分和问题的静态部分长度
    size_t headLength = sizeof(struct DNS_HEADER);
    size_t hostLength = strlen((const char*) qname) + 1;
    size_t qusetionLength = sizeof(struct QUESTION);
    
    // 定义指针,将位置移动到报文的Answer部
    unsigned char *reader = &recvBuf[headLength + hostLength + qusetionLength];

    /*
    **解析接收报文
    */
    // 加2个字节,是因为解析的数据中,域名采用的是指针方式,占两个字节(实际情况这里需要判断是否是指针还是真的域名)
    reader = reader + 2;
    // 将Answer部分的静态数据解析到结构体
    struct R_DATA answer = *((struct R_DATA*) (reader));
    printf("回答类型:%x\n", ntohs(answer.TYPE));
    printf("缓存时间:%d秒\n",ntohl(answer.TTL));
    //指向回答问题区域的资源数据字段
    reader = reader + sizeof(struct R_DATA);
    //判断资源类型是否为IPv4地址
    unsigned char *ip = NULL;
    if (ntohs(answer.TYPE) == A) {
        //解析到的IP数据 指针
        ip = (unsigned char*) malloc(ntohs(answer.RDLENGTH)+1);
        for (int j = 0; j < ntohs(answer.RDLENGTH); j++) {
            ip[j] = reader[j];
        }
        ip[ntohs(answer.RDLENGTH)] = '\0';
    }

    //显示查询结果
    if (ip) {
        long *p;
        p = (long*) ip;
        // inet_ntoa用来进行IP转换
        printf("IPv4地址:%s\n", inet_ntoa(*(struct in_addr*)ip));
    }
    return;
}

/*
**从www.baidu.com转换到3www5baidu3com
*/
int ChangetoDnsNameFormat(unsigned char* dns, unsigned char* host) {
    int lock = 0, i, length = 0;
    strcat((char*) host, ".");

    for (i = 0; i < strlen((char*) host); i++) {
        if (host[i] == '.') {
            *dns++ = i - lock;
            length ++;
            for (; lock < i; lock++) {
                *dns++ = host[lock];
                length ++;
            }
            lock++;
        }
    }
    *dns++ = '\0';
    length ++;
    return length;
}

int main(int argc, const char * argv[]) {
    unsigned char hostname[100] = "huishao.cc";
    //由域名获得IPv4地址,A是查询类型
    DNS(hostname);
    return 0;
}

上面的代码有详细的注释,你可以尝试运行下进行域名解析,需要注意,上面填写的192.168.1.1是本地路由器的域名服务器地址,你需要将其替换成自己的,当然你也可以使用通用的域名解析服务器,如114.114.114.144。上面的代码采用C语言编写,因此在处理数据的时候会有一些复杂,有些点需要注意。

1. 关于结构体位域

简单理解,位域可以让结构体中的数据以Byte为单位已经存储,例如上面定义的DNS_HEADER结构体,我们按照DNS协议的结构对其内数据所占的位进行了定义,有一点需要额外注意,在定义结构体时,位域字段的顺序与实际填充的顺序是相反的,位域的填充是从低字节开始的,如上代码所示,对于1个字节的位域来说,我们定义的时候,先定义的RD字段,最后定义的QR字段,实际在存储数据时,这一个字节的最高位会存储QR,最低位会存储RD。

2. 关于字节对齐

在定义结构体时,还有一个细节需要注意,如果结构体中的数据字节数不是一致的,则其创建的内存大小可能和实际所需要的并不一致,例如R_DATA结构体,其中有int和short类型的数据,则其会以4字节为标准进行对齐,我们需要手动设置其对齐位数,不然后续数据填充时会出现偏差。

3.网络字节序与主机字节序

网络字节序是TCP/IP协议中定义的一种数据格式,其采用的是大端(big-endian)的排序方式,即对于一个字(两个字节)的数据,低字节在前,高字节在后。这与我们可读的主机字节序刚好是相反的,在C语言中,使用htons可以把short类型的数据进行网络和主机字节序的转换,htonl把long类型的数据进行网络和主机字节序的转换。

可以在如下地址下载到完整的上述代码:

https://gitee.com/jaki/dns_c

温馨提示,上面代码中解析的域名只返回了一个A类型的解析应答,如果你解析其他域名,可能会有很多CNAME类型的应答,应答个数也可能不止一个,你可以尝试下优化下代码,完整的实现DNS的解析逻辑。

七. 结尾

本篇博客到此就结束了,我相信你对从域名获取到IP的过程有了更多的认识,如果遇到了域名解析的问题,你应该明白如何查看响应结果来定位问题了,但是,这只是我们日常使用的网络中的第一步,目前我们连应用层的核心都还没有接触到,不积跬步,无以至千里,与君共勉。

专注技术,热爱生活,交流技术,也做朋友。
目录
相关文章
|
12天前
|
机器学习/深度学习 人工智能 算法
深入解析图神经网络:Graph Transformer的算法基础与工程实践
Graph Transformer是一种结合了Transformer自注意力机制与图神经网络(GNNs)特点的神经网络模型,专为处理图结构数据而设计。它通过改进的数据表示方法、自注意力机制、拉普拉斯位置编码、消息传递与聚合机制等核心技术,实现了对图中节点间关系信息的高效处理及长程依赖关系的捕捉,显著提升了图相关任务的性能。本文详细解析了Graph Transformer的技术原理、实现细节及应用场景,并通过图书推荐系统的实例,展示了其在实际问题解决中的强大能力。
93 30
|
16天前
|
SQL 安全 算法
网络安全之盾:漏洞防御与加密技术解析
在数字时代的浪潮中,网络安全和信息安全成为维护个人隐私和企业资产的重要防线。本文将深入探讨网络安全的薄弱环节—漏洞,并分析如何通过加密技术来加固这道防线。文章还将分享提升安全意识的重要性,以预防潜在的网络威胁,确保数据的安全与隐私。
32 2
|
18天前
|
安全 算法 网络安全
网络安全的盾牌与剑:漏洞防御与加密技术深度解析
在数字信息的海洋中,网络安全是航行者不可或缺的指南针。本文将深入探讨网络安全的两大支柱——漏洞防御和加密技术,揭示它们如何共同构筑起信息时代的安全屏障。从最新的网络攻击手段到防御策略,再到加密技术的奥秘,我们将一起揭开网络安全的神秘面纱,理解其背后的科学原理,并掌握保护个人和企业数据的关键技能。
26 3
|
20天前
|
网络协议
网络通信的基石:TCP/IP协议栈的层次结构解析
在现代网络通信中,TCP/IP协议栈是构建互联网的基础。它定义了数据如何在网络中传输,以及如何确保数据的完整性和可靠性。本文将深入探讨TCP/IP协议栈的层次结构,揭示每一层的功能和重要性。
52 5
|
22天前
|
网络协议 安全 文件存储
动态DNS(DDNS)技术在当前网络环境中日益重要,它允许使用动态IP地址的设备通过固定域名访问
动态DNS(DDNS)技术在当前网络环境中日益重要,它允许使用动态IP地址的设备通过固定域名访问,即使IP地址变化,也能通过DDNS服务保持连接。适用于家庭网络远程访问设备及企业临时或移动设备管理,提供便捷性和灵活性。示例代码展示了如何使用Python实现基本的DDNS更新。尽管存在服务可靠性和安全性挑战,DDNS仍极大提升了网络资源的利用效率。
44 6
|
20天前
|
监控 网络协议 网络性能优化
网络通信的核心选择:TCP与UDP协议深度解析
在网络通信领域,TCP(传输控制协议)和UDP(用户数据报协议)是两种基础且截然不同的传输层协议。它们各自的特点和适用场景对于网络工程师和开发者来说至关重要。本文将深入探讨TCP和UDP的核心区别,并分析它们在实际应用中的选择依据。
48 3
|
24天前
|
SQL 监控 安全
网络安全的盾牌与利剑:漏洞防御与加密技术解析
在数字时代的洪流中,网络安全如同一场没有硝烟的战争。本文将深入探讨网络安全的核心议题,从网络漏洞的发现到防御策略的实施,以及加密技术的运用,揭示保护信息安全的关键所在。通过实际案例分析,我们将一窥网络攻击的手段和防御的艺术,同时提升个人与企业的安全意识,共同构筑一道坚固的数字防线。
|
27天前
|
安全 算法 网络安全
网络安全的盾牌与剑:漏洞防御与加密技术解析
【10月更文挑战第42天】在数字时代的海洋中,网络安全是守护数据宝藏的坚固盾牌和锋利之剑。本文将揭示网络安全的两大支柱——漏洞防御和加密技术,通过深入浅出的方式,带你了解如何发现并堵塞安全漏洞,以及如何使用加密技术保护信息不被窃取。我们将一起探索网络安全的奥秘,让你成为信息时代的智者和守护者。
34 6
|
27天前
|
存储 SQL 安全
网络安全的屏障与钥匙:漏洞防御与加密技术解析
【10月更文挑战第42天】在数字时代的浪潮中,网络安全成为守护个人隐私与企业数据不被侵犯的关键防线。本文将深入探讨网络安全中的两大核心议题——漏洞防御和加密技术。我们将从网络漏洞的识别开始,逐步揭示如何通过有效的安全策略和技术手段来防范潜在的网络攻击。随后,文章将转向加密技术的奥秘,解读其在数据传输和存储过程中保护信息安全的作用机制。最后,强调提升个人和企业的安全意识,是构建坚固网络安全屏障的重要一环。
|
25天前
|
SQL 人工智能 安全
网络安全的盾牌:漏洞防护与加密技术解析
在数字时代的浪潮中,网络安全和信息安全成为了维护社会稳定和保护个人隐私的关键。本文将深入探讨网络安全中的常见漏洞、先进的加密技术以及提升安全意识的重要性。通过分析网络攻击的手法,揭示防御策略的构建过程,并分享实用的代码示例,旨在为读者提供一套全面的网络安全知识体系,以增强个人和组织在网络空间的防御能力。

相关产品

  • 云解析DNS
  • 推荐镜像

    更多