第七章 W55MH32 DNS解析域名示例

目录

1 DNS协议简介

2 DNS域名介绍

3 DNS查询方式

4 DNS协议的基本工作流程

5 DNS报文

6 实现过程

7 运行结果

8 总结


本篇文章,我们将详细介绍如何在W55MH32芯片上面实现DNS域名解析功能。并通过实战例程,为大家讲解如何将wiznet.io的域名解析为实际IP地址,供大家参考。

该例程用到的其他网络协议,例如DHCP,请参考相关章节。有关W55MH32的初始化过程,也请参考相关章节,这里将不再赘述。

1 DNS协议简介

在学习DNS协议之前,我们先区分一下IP地址和域名这两个概念:

  1. IP地址:一长串能够唯一地标记网络上地计算机的数字。
  2. 域名:又称网域,是由一串用点分隔的名字组成的Internet上某一台计算机或计算机组的名称,用于在数据传输时对计算机的定位标识,例如:wiznet.io。

如何理解域名和网址的概念,可以这么理解,网址里面包含域名。举个例子:https://wiznet.io/Products就是一个网址,而wiznet.io就是域名。

因为 IP 地址具有不方便记忆并且不能显示地址组织的名称和性质等缺点,所以设计出了域名,并通过域名解析协议(DNSDomain Name System)来将域名和 IP 地址相互映射,使人能够更方便地访问互联网,而不用去记住能够被机器直接读取的 IP 地址数串。将域名映射成 IP 地址称为DNS正向解析,将 IP 地址映射成域名称为DNS反向解析。

DNS协议可以使用UDP或者TCP进行传输,使用的端口号都为53,但大多数情况下DNS都是用UDP进行传输。

以上是DNS协议的简介,如想深入了解该协议,请参考mozilla网站上的介绍: DNS - MDN Web 文档术语表:Web 相关术语的定义 | MDN

2 DNS域名介绍

DNS域名通常分为以下几类:

  1. 根域名服务器:根域名服务器是DNS系统的顶层,负责管理整个DNS命名空间的根区(Root Zone)。它主要用于引导查询,指向顶级域(TLD)的权威服务器。
  2. 顶级域名服务器:负责特定顶级域(如.com、.org、.net)或国家/地区代码顶级域(ccTLD,如.cn、.uk)的解析。
  3. 权威DNS服务器:负责存储并提供特定域名的DNS记录信息
  4. 本地DNS服务器:本地域名服务器是电脑解析时的默认域名服务器,即电脑中设置的首选 DNS 服务器和备选 DNS 服务器。常见的有电信、联通、谷歌、阿里等的本地 DNS 服务。

3 DNS查询方式

DNS查询方式分为以下两种:

  1. 递归查询:指由DNS客户端(如用户设备或本地域名服务器)向DNS服务器发起的查询请求,DNS服务器负责全程完成查询过程,并将最终的解析结果返回给客户端。
  2. 迭代查询:指DNS服务器返回给客户端或请求者的下一步建议,而不是直接返回最终结果,由客户端自行完成多次查询,逐步获取解析结果。

下面两张图则是递归查询和迭代查询的工作流程图。

4 DNS协议的基本工作流程

接下来,我们以PC端正向解析www.baidu.com为例,了解下DNS解析的工作流程。

1)首先搜索「浏览器的 DNS 缓存」,缓存中维护一张域名与 IP 地址的对应表;

2)若没有命中,则继续搜索「操作系统的 DNS 缓存」

3)若仍然没有命中,则操作系统将域名发送至「本地域名服务器」,本地域名服务器查询自己的 DNS 缓存,查找成功则返回结果(注意:主机和本地域名服务器之间的查询方式是「递归查询」);

4)若本地域名服务器的 DNS 缓存没有命中,则本地域名服务器向上级域名服务器进行查询,通过以下方式进行「迭代查询」(注意:本地域名服务器和其他域名服务器之间的查询方式是迭代查询,防止根域名服务器压力过大):

  1. 首先本地域名服务器向「根域名服务器」发起请求,根域名服务器是最高层次的,它并不会直接指明这个域名对应的 IP 地址,而是返回顶级域名服务器的地址,也就是说给本地域名服务器指明一条道路,让他去这里寻找答案。
  2. 本地域名服务器拿到这个「顶级域名服务器」的地址后,就向其发起请求,获取「权限域名服务器」的地址
  3. 本地域名服务器根据权限域名服务器的地址向其发起请求,最终得到该域名对应的 IP 地址

5)本地域名服务器将得到的 IP 地址返回给操作系统,同时自己将 IP 地址缓存起来

6)操作系统将 IP 地址返回给浏览器,同时自己也将 IP 地址缓存起来

7)至此,浏览器就得到了域名对应的 IP 地址,并将 IP 地址缓存起来

配合下图直观理解:

在W55MH32上使用DNS正向解析wiznet.io域名时,我们只需要向本地域名服务器发送DNS请求报文,然后解析DNS响应报文即可。

5 DNS报文

DNS报文分为以下五个部分:

  1. 报文头部:定义了请求或响应的元信息(如标志、条目数等)。
  2. 问题区域:描述了查询的域名和查询类型。
  3. 回答区域:包含查询的最终结果(如域名对应的IP地址)。
  4. 权威区域:提供权威DNS服务器的信息。
  5. 附加区域:包含附加的相关信息(如域名的A记录)。

DNS请求报文主要由报文头部和问题区域组成,回答区域、权威区域和附加区域为空。

  1. 报文头部
  1. Transaction ID:固定长度为16bit,唯一标识符,用于匹配请求和响应。
  2. Flags:固定长度为16bit,标志位(例如查询类型、递归期望等)。
  3. Questions:固定长度为16bit,问题区域的条目数,通常为1。
  4. Answer RRs:固定长度为16bit,回答区域的条目数,查询报文中为0。
  5. Authority RRs:固定长度为16bit,权威区域的条目数,查询报文中为0。
  6. Additional RRs:固定长度为16bit,附加区域的条目数,查询报文中为0。
  1. 问题区域
  1. QName:查询的域名(以点分形式存储)。
  2. QType:查询的记录类型(如A记录、AAAA记录、MX记录等)。
  3. QClass:查询的记录类别,通常为IN(互联网)。

DNS响应报文包含与请求报文类似的头部和问题区域,并附加回答、权威和附加区域信息。

  1. 报文头部:同请求报文,但Flags内容有所变化:
  1. QR:1表示响应(查询报文中为0)
  2. RCODE:返回码,表示响应状态(如0表示无错误,3表示域名不存在)。
  3. AA:权威回答标志(1表示这是权威服务器返回的响应)。
  1. 问题区域:与请求报文一致,用于描述客户端的查询。
  2. 回答区域:包含查询结果,如域名对应的IP地址。每条回答包含以下字段:
  1. Name:对应的域名
  2. Type:记录类型(如A、AAAA、CNAME等)。
  3. Class:记录类别(通常为IN)。
  4. TTL:记录的生存时间(秒)。
  5. Rdata:记录的具体值(如IP地址)。
  1. 权威区域:提供权威服务器的信息,通常包含NS记录。
  2. 附加区域:包含额外的解析信息,如权威服务器的A记录和AAAA记录。

请求报文实例:请求解析域名wiznet.ioA记录

| 报文头部 |

Transaction ID: 0x8D12

Flags: 0x0100 (标准查询、期望递归)

Questions: 1

Answer RRs: 0

Authority RRs: 0

Additional RRs: 0

| 问题区域 |

QName:wiznet.io

QType: A

QClass: IN

| 报文原文 |

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

响应报文实例:DNS服务器返回wiznet.io的A记录解析结果(IP为183.111.138.249

| 报文头部 |

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

| 报文原文 |

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

6 实现过程

接下来,我们看看如何在W55MH32上实现DNS正向解析。

注意:因为本示例需要访问互联网,请确保W55MH32的配置能够访问互联网。

步骤一:注册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等内容:

#ifndef         _DNS_H_
#define         _DNS_H_

#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>
/*
 * @brief Define it for Debug & Monitor DNS processing.
 * @note If defined, it dependens on <stdio.h>
 */
//#define _DNS_DEBUG_

#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

步骤二:进行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;
}

7 运行结果

烧录例程运行后,首先进行了PHY链路检测,然后是DHCP获取网络地址结果,最后是DNS成功解析出wiznet.io的IP地址为183.111.138.249,如下图所示:

8 总结

本文介绍在 W55MH32 芯片上实现 DNS 域名解析功能的方法,讲解如何将 wiznet.io 域名解析为实际 IP 地址。阐述了 DNS 协议发热概念、域名分类、查询方式和工作流程,介绍了 DNS 报文结构及请求、响应报文实例等。展示在W55MH32上的实现过程。

下一篇将讲解在该芯片上实现 HTTP Client 功能,介绍向指定网站提交数据的原理和实现步骤。敬请期待!

### STM32W55BCGU6低功耗特性配置 #### 一、STM32W55BCGU6概述 STM32系列微控制器以其高性能和丰富的外设而闻名,其中STM32W55BCGU6型号继承了该家族的优势并特别注重低功耗应用的设计。此款MCU不仅具备强大的处理能力,而且通过多种机制实现了高效的能量管理,在休眠模式下仍能维持必要的外围设备运作。 #### 二、低功耗特性 为了满足不同应用场景的需求,STM32W55BCGU6提供了多样化的省电选项: - **待机模式(Standby Mode)**:当系统处于长时间无活动状态时可启用这种最深程度的节能方式;此时除了RTC之外几乎所有的电路都会被关闭以减少静态电流消耗。 - **停止模式(STOP Mode)**:允许CPU暂停工作但保留SRAM中的数据以及一些基本定时器的功能,以便快速唤醒恢复执行任务。在此期间可以利用外部中断或事件来触发重新激活过程[^4]。 - **睡眠模式(Sleep Mode)**:这是相对较浅的一种节省电量的方法,核心频率降低至最低限度但仍保持足够的响应速度用于即时处理突发情况下的请求。 #### 三、具体配置方法 针对上述提到的各种低功率运行状况,可以通过调整软件设置来进行优化控制: 1. 对于进入STOP模式的情况,开发者可以根据实际需求选择合适的入口函数(`PWR_STOPEntry_WFI` 或 `PWR_STOPEntry_WFE`) 来决定等待条件是特定类型的中断还是任意发生的事件。 ```c if(PWR_STOPEntry == PWR_STOPEntry_WFI) { /* Request Wait For Interrupt */ __WFI(); } else { /* Request Wait For Event */ __WFE(); } ``` 2. 当涉及到更深层次如Standby模式,则需调用相应API接口完成电源管理和实时时钟(RTC)初始化等工作,并确保在此之前保存好所有重要变量的状态信息以防丢失。 3. 此外还有其他参数可供调节比如电压规模(VOS),主PLL分频系数等都对最终达到的效果有着直接影响,因此建议参照官方文档详细了解各项指令的具体含义及其作用范围后再做设定。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值