DNS
本篇文章,我们将详细介绍如何在W55MH32芯片上面实现DNS域名解析功能。并通过实战例程,为大家讲解如何将wiznet.io的域名解析为实际IP地址,供大家参考。
该例程用到的其他网络协议,例如DHCP,请参考相关章节。有关W55MH32的初始化过程,请参考Network Install章节,这里将不再赘述。
DNS协议简介
在学习DNS协议之前,我们先区分一下IP地址和域名这两个概念:
- IP地址:一长串能够唯一地标记网络上地计算机的数字。
- 域名:又称网域,是由一串用点分隔的名字组成的Internet上某一台计算机或计算机组的名称,用于在数据传输时对计算机的定位标识,例如:wiznet.io。
如何理解域名和网址的概念,可以这么理解,网址里面包含域名。举个例子:https://wiznet.io/Products就是一个网址,而wiznet.io就是域名。 因为 IP 地址具有不方便记忆并且不能显示地址组织的名称和性质等缺点,所以设计出了域名,并通过域名解析协议(DNS,Domain Name System)来将域名和 IP 地址相互映射,使人能够更方便地访问互联网,而不用去记住能够被机器直接读取的 IP 地址数串。将域名映射成 IP 地址称为DNS正向解析,将 IP 地址映射成域名称为DNS反向解析。
DNS协议可以使用UDP或者TCP进行传输,使用的端口号都为53,但大多数情况下DNS都是用UDP进行传输。
以上是DNS协议的简介,如想深入了解该协议,请参考mozilla网站上的介绍: DNS - MDN Web 文档术语表:Web 相关术语的定义 | MDN
DNS域名介绍
DNS域名通常分为以下几类:
- 根域名服务器:根域名服务器是DNS系统的顶层,负责管理整个DNS命名空间的根区(Root Zone)。它主要用于引导查询,指向顶级域(TLD)的权威服务器。
- 顶级域名服务器:负责特定顶级域(如.com、.org、.net)或国家/地区代码顶级域(ccTLD,如.cn、.uk)的解析。
- 权威DNS服务器:负责存储并提供特定域名的DNS记录信息。
- 本地DNS服务器:本地域名服务器是电脑解析时的默认域名服务器,即电脑中设置的首选 DNS 服务器和备选 DNS 服务器。常见的有电信、联通、谷歌、阿里等的本地 DNS 服务。
DNS查询方式
DNS查询方式分为以下两种:
- 递归查询指由DNS客户端(如用户设备或本地域名服务器)向DNS服务器发起的查询请求,DNS服务器负责全程完成查询过程,并将最终的解析结果返回给客户端。
- 迭代查询指DNS服务器返回给客户端或请求者的下一步建议,而不是直接返回最终结果,由客户端自行完成多次查询,逐步获取解析结果。
下面两张图则是递归查询和迭代查询的工作流程图。


DNS协议的基本工作流程
- 首先搜索「浏览器的 DNS 缓存」,缓存中维护一张域名与 IP 地址的对应表;
- 若没有命中,则继续搜索「操作系统的 DNS 缓存」;
-
若仍然没有命中,则操作系统将域名发送至「本地域名服务器」,本地域名服务器查询自己的 DNS缓存,查找成功则返回结果;
请注意:
主机和本地域名服务器之间的查询方式是「递归查询」
-
若本地域名服务器的
DNS缓存没有命中,则本地域名服务器向上级域名服务器进行查询,通过以下方式进行「迭代查询」
请注意:
本地域名服务器和其他域名服务器之间的查询方式是「迭代查询」,防止根域名服务器压力过大
- 首先本地域名服务器向「根域名服务器」发起请求,根域名服务器是最高层次的,它并不会直接指明这个域名对应的 IP 地址,而是返回顶级域名服务器的地址,也就是说给本地域名服务器指明一条道路,让他去这里寻找答案。
- 本地域名服务器拿到这个「顶级域名服务器」的地址后,就向其发起请求,获取「权限域名服务器」的地址
- 本地域名服务器根据权限域名服务器的地址向其发起请求,最终得到该域名对应的 IP 地址
- 嵌入式Web服务器:一些嵌入式设备内置Web服务器(例如路由器、网关、传感器设备等),通过TCP协议提供网页接口给用户进行配置和监控。
- 本地域名服务器将得到的 IP 地址返回给操作系统,同时自己将 IP 地址缓存起来;
- 操作系统将 IP 地址返回给浏览器,同时自己也将 IP 地址缓存起来;
- 至此,浏览器就得到了域名对应的 IP 地址,并将 IP 地址缓存起来;
上文配合下图更直观理解DNS工作过程。

在W55MH32上使用DNS正向解析wiznet.io域名时,我们只需要向本地域名服务器发送DNS请求报文,然后解析DNS响应报文即可。
DNS报文
DNS报文分为以下五个部分:
- 报文头部:定义了请求或响应的元信息(如标志、条目数等)。
- 问题区域:描述了查询的域名和查询类型。
- 回答区域:包含查询的最终结果(如域名对应的IP地址)。
- 权威区域:提供权威DNS服务器的信息。
- 附加区域:包含附加的相关信息(如域名的A记录)。
DNS请求报文主要由报文头部和问题区域组成,回答区域、权威区域和附加区域为空。
-
报文头部
- Transaction ID:固定长度为16bit,唯一标识符,用于匹配请求和响应。
- Flags:固定长度为16bit,标志位(例如查询类型、递归期望等)。
- Questions:固定长度为16bit,问题区域的条目数,通常为1。
- Answer RRs:固定长度为16bit,回答区域的条目数,查询报文中为0。
- Authority RRs:固定长度为16bit,权威区域的条目数,查询报文中为0。
- Additional RRs:固定长度为16bit,附加区域的条目数,查询报文中为0。
-
问题区域
- QName:查询的域名(以点分形式存储)。
- QType:查询的记录类型(如A记录、AAAA记录、MX记录等)。
- QClass:查询的记录类别,通常为IN(互联网)。
DNS响应报文包含与请求报文类似的头部和问题区域,并附加回答、权威和附加区域信息。
-
报文头部:同请求报文,但Flags内容有所变化。
- QR:1表示响应(查询报文中为0)
- RCODE:返回码,表示响应状态(如0表示无错误,3表示域名不存在)。
- AA:权威回答标志(1表示这是权威服务器返回的响应)。
- 问题区域:与请求报文一致,用于描述客户端的查询。
-
回答区域:包含查询结果,如域名对应的IP地址。每条回答包含以下字段
- Name对应的域名
- Type:记录类型(如A、AAAA、CNAME等)。
- Class:记录类别(通常为IN)。
- TTL:记录的生存时间(秒)。
- Rdata:记录的具体值(如IP地址)。
- 权威区域:提供权威服务器的信息,通常包含NS记录。
- 附加区域:包含额外的解析信息,如权威服务器的A记录和AAAA记录。
请求报文实例:请求解析域名wiznet.io的A记录
| 报文原文 |
8D 12 01 00 00 01 00 00 00 00 00 00
06 77 69 7A 6E 65 74 02 69 64 00 00 01 00 01
| 报文解析 |
Transaction ID: 0x8D12
Flags: 0x0100 (标准查询、期望递归)
Questions: 1
Answer RRs: 0
Authority RRs: 0
Additional RRs: 0
| 问题区域 |
QName:wiznet.io
QType: A
QClass: IN
响应报文实例:DNS服务器返回wiznet.io的A记录解析结果(IP为183.111.138.249)
| 报文原文 |
8D 12 81 80 00 01 00 01 00 00 00 00
06 77 69 7A 6E 65 74 02 69 6F 00 00 01 00 01
C0 0C 00 01 00 01 00 00 00 9C 00 04 B7 6F 8A F9
| 报文解析 |
Transaction ID: 0x8D12
Flags: 0x8180 (响应、无错误)
Questions: 1
Answer RRs: 1
Authority RRs: 0
Additional RRs: 0
| 问题区域 |
QName:wiznet.io
QType: A
QClass: IN
| 回答区域 |
Name:wiznet.io
Type: A
Class: IN
TTL: 156
RData: 183.111.138.249
实现过程
接下来,我们看看如何在W55MH32上实现DNS正向解析。
步骤1:注册DNS定时器中断到1s定时器中
/**
* @brief 1ms timer IRQ Handler
* @param none
* @return none
*/
void TIM3_IRQHandler(void)
{
static uint32_t tim3_1ms_count = 0;
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
tim3_1ms_count++;
if (tim3_1ms_count >= 1000)
{
DHCP_time_handler();
DNS_time_handler();
tim3_1ms_count = 0;
}
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
}
注册DNS定时器中断主要为了DNS超时处理。
在dns.h文件中,定义了DNS超时时间、重试次数、端口号和消息ID等内容:
#define MAX_DNS_BUF_SIZE 256 ///< maximum size of DNS buffer. */
/*
* @brief Maxium length of your queried Domain name
* @todo SHOULD BE defined it equal as or greater than your Domain name lenght + null character(1)
* @note SHOULD BE careful to stack overflow because it is allocated 1.5 times as MAX_DOMAIN_NAME in stack.
*/
#define MAX_DOMAIN_NAME 128 // for example "www.google.com"
#define MAX_DNS_RETRY 2 ///< Requery Count
#define DNS_WAIT_TIME 3 ///< Wait response time. unit 1s.
#define IPPORT_DOMAIN 53 ///< DNS server port number
#define DNS_MSG_ID 0x1122 ///< ID for DNS message. You can be modifyed it any number
步骤2:进行DNS正向解析处理
在do_dns()函数中,我们实现了dns正向解析的过程。
do_dns(ethernet_buf, dns_name, ip_fromdns);
这个函数的三个传参分别为DNS解析所需缓存,带解析域名,解析后的IP地址。
do_dns()函数的内容如下:
/**
* @brief DNS domain name resolution
* @param ethernet_buff: ethernet buffer
* @param domain_name:Domain name to be resolved
* @param domain_ip:Resolved Internet Protocol Address
* @return 0:success;-1:failed
*/
int do_dns(uint8_t *buf, uint8_t *domain_name, uint8_t *domain_ip)
{
int dns_ok_flag = 0;
int dns_run_flag = 1;
wiz_NetInfo net_info;
uint8_t dns_retry_cnt = 0;
DNS_init(0, buf); // DNS client init
wizchip_getnetinfo(&net_info);
while (1)
{
switch (DNS_run(net_info.dns, domain_name, domain_ip)) // Read the DNS_run return value
{
case DNS_RET_FAIL: // The DNS domain name is successfully resolved
{
if (dns_retry_cnt < DNS_RETRY) // Determine whether the parsing is successful or whether the parsing exceeds the number of times
{
dns_retry_cnt++;
}
else
{
printf("> DNS Failed\r\n");
dns_ok_flag = -1;
dns_run_flag = 0;
}
break;
}
case DNS_RET_SUCCESS: {
printf("> Translated %s to %d.%d.%d.%d\r\n", domain_name, domain_ip[0], domain_ip[1], domain_ip[2], domain_ip[3]);
dns_ok_flag = 0;
dns_run_flag = 0;
break;
}
}
if (dns_run_flag != 1)
{
return dns_ok_flag;
}
}
}
首先会调用DNS_init()函数初始化DNS配置:
/* DNS CLIENT INIT */
void DNS_init(uint8_t s, uint8_t * buf)
{
DNS_SOCKET = s; // SOCK_DNS
pDNSMSG = buf; // User's shared buffer
DNS_MSGID = DNS_MSG_ID;
}
然后是在DNS主循环中运行DNS执行函数DNS_run,它的主要作用是进行DNS组包,发送请求,响应内容解析以及超时处理,这里只需要根据DNS_run()函数的返回值进行相应处理即可。
DNS_run()函数内容如下:
/* DNS CLIENT RUN */
int8_t DNS_run(uint8_t * dns_ip, uint8_t * name, uint8_t * ip_from_dns)
{
int8_t ret;
struct dhdr dhp;
uint8_t ip[4];
uint16_t len, port;
int8_t ret_check_timeout;
retry_count = 0;
dns_1s_tick = 0;
// Socket open
socket(DNS_SOCKET, Sn_MR_UDP, 0, 0);
#ifdef _DNS_DEBUG_
printf("> DNS Query to DNS Server : %d.%d.%d.%d\r\n", dns_ip[0], dns_ip[1], dns_ip[2], dns_ip[3]);
#endif
len = dns_makequery(0, (char *)name, pDNSMSG, MAX_DNS_BUF_SIZE);
sendto(DNS_SOCKET, pDNSMSG, len, dns_ip, IPPORT_DOMAIN);
while (1)
{
if ((len = getSn_RX_RSR(DNS_SOCKET)) > 0)
{
if (len > MAX_DNS_BUF_SIZE) len = MAX_DNS_BUF_SIZE;
len = recvfrom(DNS_SOCKET, pDNSMSG, len, ip, &port);
#ifdef _DNS_DEBUG_
printf("> Receive DNS message from %d.%d.%d.%d(%d). len = %d\r\n", ip[0], ip[1], ip[2],
ip[3],port,len);
#endif
ret = parseDNSMSG(&dhp, pDNSMSG, ip_from_dns);
break;
}
// Check Timeout
ret_check_timeout = check_DNS_timeout();
if (ret_check_timeout < 0) {
#ifdef _DNS_DEBUG_
printf("> DNS Server is not responding : %d.%d.%d.%d\r\n", dns_ip[0], dns_ip[1], dns_ip[2], dns_ip[3]);
#endif
close(DNS_SOCKET);
return 0; // timeout occurred
}
else if (ret_check_timeout == 0) {
#ifdef _DNS_DEBUG_
printf("> DNS Timeout\r\n");
#endif
sendto(DNS_SOCKET, pDNSMSG, len, dns_ip, IPPORT_DOMAIN);
}
}
close(DNS_SOCKET);
// Return value
// 0 > : failed / 1 - success
return ret;
}
运行结果
请注意:
因为本示例需要访问互联网,请确保W55MH32的配置能够访问互联网。
烧录例程运行后,首先进行了PHY链路检测,然后是DHCP获取网络地址结果,最后是DNS成功解析出wiznet.io的IP地址为183.111.138.249,如下图所示:

总结
本文介绍在 W55MH32 芯片上实现 DNS 域名解析功能的方法,讲解如何将 wiznet.io 域名解析为实际 IP 地址。阐述了 DNS 协议概念、域名分类、查询方式和工作流程,介绍了 DNS 报文结构及请求、响应报文实例等。展示在W55MH32上的实现过程。
下一篇将讲解在该芯片上实现 HTTP Client 功能,介绍向指定网站提交数据的原理和实现步骤。敬请期待!