一、DNS 基础原理
1. 为什么需要 DNS?
互联网中设备通信依赖 IP 地址(如 183.232.231.174),但 IP 地址一串数字难记,人类更习惯用域名(如www.baidu.com)访问服务。DNS 的核心作用就是域名与 IP 地址的双向映射,同时提供负载均衡、容错等扩展能力,让用户无需记忆 IP,就能顺畅访问互联网服务。
2. DNS 核心工作流程
以 "访问www.baidu.com" 为例,DNS 解析流程分 6 步,结合递归查询(用户侧)和迭代查询(服务器侧):
- 本地缓存查询:用户浏览器先查本地 DNS 缓存(系统缓存、浏览器缓存),若有该域名的 IP 映射,直接返回结果,无需后续步骤;
- 本地 DNS 服务器查询:若本地缓存无结果,请求发送到本地 DNS 服务器(通常由运营商分配,如 114.114.114.114),本地 DNS 先查自身缓存,有结果则返回;
- 根服务器迭代查询:本地 DNS 无缓存时,向根 DNS 服务器(全球共 13 组)发起请求,根服务器不存储具体域名 IP,仅返回对应顶级域(如.com)的权威服务器地址;
- 顶级域服务器迭代查询:本地 DNS 向顶级域服务器(.com 服务器)请求,顶级域服务器返回目标域名(baidu.com)的权威服务器地址;
- 权威服务器查询:本地 DNS 向baidu.com的权威服务器请求,权威服务器存储该域名的 IP 映射,返回具体 IP;
- 结果缓存与返回:本地 DNS 将 IP 映射存入自身缓存(设置超时时间),同时返回给浏览器,浏览器缓存后发起 HTTP 请求,完成访问。
3. DNS 核心概念与记录类型
核心概念
- 递归查询:客户端发起请求后,服务器全程处理,最终返回结果(用户→本地 DNS);
- 迭代查询:服务器仅返回下一级服务器地址,由请求方自行向下查询(本地 DNS→根→顶级域→权威服务器);
- TTL(生存时间):DNS 记录的缓存有效期(单位秒),超时后缓存失效,需重新查询,避免 IP 变更导致解析异常。
常用 DNS 记录类型
- A 记录: IPv4 地址映射(最常用,如www.baidu.com→183.232.231.174);
- AAAA 记录:IPv6 地址映射,对应 IPv6 场景;
- CNAME 记录:别名记录,将域名指向另一个域名(如blog.baidu.com→www.baidu.com),无直接 IP 映射;
- MX 记录:邮件交换记录,指定接收该域名邮件的服务器地址(如 @baidu.com的邮件服务器);
- NS 记录:名称服务器记录,指定该域名的权威服务器地址。
二、DNS 高频考点
1. DNS 使用什么端口和协议?
默认用 UDP 53 端口(普通解析,报文小、效率高);当报文超过 512 字节(如返回多个 IP)或进行区域传输(服务器间同步 DNS 记录)时,用 TCP 53 端口。
2. 本地 DNS 服务器的作用是什么?
核心:缓存解析结果,减少重复查询,提升解析效率;代理客户端发起迭代查询,降低用户侧复杂度。
3. DNS 解析的顺序是什么?
浏览器缓存→系统缓存→本地 DNS 服务器缓存→根服务器→顶级域服务器→权威服务器。
4. DNS 劫持(域名劫持)的原理及防御?
原理:攻击者篡改 DNS 解析结果,将域名指向恶意 IP(如钓鱼网站),常见方式有本地 HOSTS 文件篡改、路由器 DNS 劫持、本地 DNS 服务器污染;
防御:① 改用公共 DNS(如 8.8.8.8、1.1.1.1);② 开启 DNS 加密(DNS over HTTPS/DoH);③ 定期检查 HOSTS 文件和路由器设置。
5. DNS 负载均衡的实现方式?
核心:权威服务器返回多个目标 IP,本地 DNS 按一定规则(轮询、权重)选择 IP,实现请求分发;补充:CDN 的核心就是结合 DNS 负载均衡,将用户解析到最近的节点。
6. 什么是 DNS 污染(DNS 缓存投毒)?
原理:攻击者向本地 DNS 服务器注入虚假 DNS 记录,污染缓存,导致后续解析返回错误 IP;与 DNS 劫持的区别:污染针对服务器缓存,劫持针对客户端 / 传输链路。
7. DoH 和 DoT 是什么?
均为 DNS 加密协议,解决传统 DNS 明文传输易被监听、篡改的问题:DoH(DNS over HTTPS)通过 HTTPS 传输 DNS 请求,端口 443;DoT(DNS over TLS)通过 TLS 协议传输,端口 853。
8. DNS 是应用层协议?
是应用层协议,依赖 UDP/TCP 传输,属于 TCP/IP 协议栈的应用层;
9. 根服务器存储所有域名 IP?
根服务器仅负责指向顶级域服务器,不存储具体域名映射;
10. CNAME 记录可以和 A 记录共存?
同一域名不能同时设置 A 记录和 CNAME 记录,需二选一。
三、DNS 域名解析实现
1. 环境与依赖
- 环境:Windows(MinGW/VS)、Linux(Ubuntu/CentOS)通用;
- 依赖:无需额外第三方库,使用系统自带的
getaddrinfo函数(跨平台、易上手,适合小白),该函数封装了 DNS 解析逻辑,无需手动处理协议细节; - 核心:
getaddrinfo支持 IPv4/IPv6 解析,自动处理 DNS 缓存,简化开发流程。
2. 基础域名解析
实现功能:输入域名(如www.baidu.com),解析并输出对应的 IPv4 地址,代码加详细注释,小白可直接复制运行。
cpp
#include <string<sys/socket.h>
#include <netdb.h>
#include<arpa/inet.h<cstring>
// 域名解析函数:输入域名,输出IPv4地址
std::string dns_resolve(const std::string& domain) {
struct addrinfo hints; // 解析参数设置
struct addrinfo* result; // 解析结果
struct addrinfo* ptr; // 遍历结果的指针
char ip_str[INET_ADDRSTRLEN];// 存储IPv4地址字符串
// 初始化hints结构体,指定解析类型为IPv4、TCP
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET; // AF_INET=IPv4,AF_INET6=IPv6
hints.ai_socktype = SOCK_STREAM; // TCP类型(不影响DNS解析,仅指定协议族)
// 调用getaddrinfo执行DNS解析,0表示成功
int ret = getaddrinfo(domain.c_str(), nullptr, &hints, &result);
if (ret != 0) {
std::< "DNS解析失败< gai_strerror< std::endl;
return "";
}
// 遍历解析结果,提取第一个IPv4地址
std::string ip;
for (ptr = result; ptr != nullptr; ptr = ptr->ai_next) {
// 将二进制IP转换为字符串格式
inet_ntop(ptr->ai_family,
&((struct sockaddr_in*)ptr->ai_addr)->sin_addr,
ip_str, sizeof(ip_str));
ip = ip_str;
break; // 取第一个IP,可扩展为返回所有IP
}
freeaddrinfo(result); // 释放结果内存,避免内存泄漏
return ip;
}
int main() {
std::string domain;
std< "请输入要解析的域名:";
std::cin >> domain;
std::string ip = dns_resolve(domain);
if (!ip.empty()) {
std::cout << " 的IPv4地址:" << ip << std::endl;
}
return 0;
}
编译与运行
- Linux:
g++ dns_demo.cpp -o dns_demo,运行./dns_demo,输入域名即可得到 IP; - Windows(VS):直接创建控制台项目,粘贴代码编译运行,无需额外配置。
3. 支持多 IP、IPv6 解析
基于基础版本扩展,实现同时返回多个 IPv4/IPv6 地址,适配更多场景,代码如下:
cpp
<string>
#include <vector>
#include <sys/socket.h>
<netdb.h><arpa/inet.h>
#include <cstring>
// 解析域名,返回所有IPv4/IPv6地址
<std::string> dns_resolve_all(const std::string& domain, bool is_ipv6 = false) {
struct addrinfo hints;
struct addrinfo* result;
struct addrinfo* ptr;
std<std::string> ips;
memset(&hints, 0, sizeof(hints));
// 选择IPv4或IPv6
hints.ai_family = is_ipv6 ? AF_INET6 : AF_INET;
hints.ai_socktype = SOCK_STREAM;
int ret = getaddrinfo(domain.c_str(), nullptr, &hints, &result);
if (ret != 0) {
std< "DNS解析< gai_str< std::endl;
return ips;
}
// 遍历所有解析结果,收集IP
char ip_str[INET6_ADDRSTRLEN]; // 适配IPv6最长长度
for (ptr = result; ptr != nullptr; ptr = ptr->ai_next) {
if (is_ipv6) {
inet_ntop(ptr->ai_family,
&((struct sockaddr_in6*)ptr->ai_addr)->sin6_addr,
ip_str, sizeof(ip_str));
} else {
inet_ntop(ptr->ai_family,
&((struct sockaddr_in*)ptr->ai_addr)->sin_addr,
ip_str, sizeof(ip_str));
}
ips.push_back(ip_str);
}
freeaddrinfo(result);
return ips;
}
int main() {
std::string domain;
< "请输入要解析的域名:";
std::cin >> domain;
// 解析IPv4地址
<std::string> ipv4_ips = dns_resolve_all(domain, false);
if (!ipv4_ips.empty()) {
std::cout << domain << " 的IPv4地址:< std::endl;
for (const auto& ip : ipv4_ips) {
std::cout << "- " << std::endl;
}
}
// 解析IPv6地址
std::<std::string> ipv6_ips = dns_resolve_all(domain, true);
if (!ipv6_ips.empty()) {
std::cout< " 的IPv< std::endl;
for (const auto& ip : ipv6_ips) {
std::cout << std::endl;
}
}
return 0;
}
4. 扩展
- 结合缓存:添加本地缓存逻辑(用哈希表存储域名 - IP 映射,设置 TTL),减少重复解析,模拟本地 DNS 缓存功能;
- 批量解析:读取文件中的多个域名,批量解析并输出结果,生成解析报告;
- 自定义 DNS 服务器:指定解析使用的 DNS 服务器(如 8.8.8.8),而非系统默认,需结合
socket手动发送 DNS UDP 请求,深入底层协议; - 异常处理强化:添加超时机制、重试逻辑,处理网络异常、域名不存在等场景。