网站网站设计,老的网站为什么要改版新网站,濮阳建设公司网站,wordpress简体中文版本fnettrace_dns.h 文章目录 fnettrace_dns.hmain.cprint_dnscustom_bpfprint_daterun_tracemain 补充DNSDNS协议报文格式**问题记录****资源记录****报文实例****请求报文****响应报文** DNS解析过程DNS 出现DNS 介绍DNS 域名DNS 域名组成DNS 域名空间DNS 域名解析DNS 解析器DNS…fnettrace_dns.h 文章目录 fnettrace_dns.hmain.cprint_dnscustom_bpfprint_daterun_tracemain 补充DNSDNS协议报文格式**问题记录****资源记录****报文实例****请求报文****响应报文** DNS解析过程DNS 出现DNS 介绍DNS 域名DNS 域名组成DNS 域名空间DNS 域名解析DNS 解析器DNS 域名服务器DNS 域名服务器类型DNS 域名完整解析过程DNS 传输层协议DNS 查询方式递归查询迭代查询DNS 反向查询DNS 应用场景静态域名解析动态域名解析DNS 代理动态域名解析DNS 代理 #ifndef FNETTRACE_DNS_H
#define FNETTRACE_DNS_H#include ../include/common.h
#include unistd.h
#include sys/stat.h
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include time.h
#include stdarg.h
#include fcntl.h
#include sys/mman.h#endif
main.c #include fnettrace_dns.h
#include sys/ioctl.h
#include time.h
#include linux/filter.h
#include linux/if_ether.h
#include sys/prctl.h
#include signal.h
#define MAX_BUF_SIZE (64 * 1024)static int arg_nolocal 0;
static char last[512] {\0};// pkt - start of DNS layer
void print_dns(uint32_t ip_src, unsigned char *pkt) {assert(pkt);char ip[30];sprintf(ip, %d.%d.%d.%d, PRINT_IP(ip_src));time_t seconds time(NULL);struct tm *t localtime(seconds);int nxdomain ((*(pkt 3) 0x03) 0x03)? 1: 0;// expecting a single question countif (pkt[4] ! 0 || pkt[5] ! 1)goto errout;// check cnameunsigned char *ptr pkt 12;int len 0;while (*ptr ! 0 len 255) { // 255 is the maximum length of a domain name including multiple .if (*ptr 63) // the name left of a . is 63 length maximumgoto errout;int delta *ptr 1;*ptr .;len delta;;ptr delta;}if (*ptr ! 0)goto errout;ptr;uint16_t type;memcpy(type, ptr, 2);type ntohs(type);// filter outputchar tmp[sizeof(last)];snprintf(tmp, sizeof(last), %02d:%02d:%02d %-15s DNS %s (type %u)%s,t-tm_hour, t-tm_min, t-tm_sec, ip, pkt 12 1,type, (nxdomain)? NXDOMAIN: );if (strcmp(tmp, last)) {printf(%s\n, tmp);fflush(0);strcpy(last, tmp);}return;errout:printf(%02d:%02d:%02d %15s Error: invalid DNS packet\n, t-tm_hour, t-tm_min, t-tm_sec, ip);fflush(0);
}// https://www.kernel.org/doc/html/latest/networking/filter.html
static void custom_bpf(int sock) {struct sock_filter code[] {// sudo tcpdump ip and udp and src port 53 -dd{ 0x28, 0, 0, 0x0000000c },{ 0x15, 0, 8, 0x00000800 },{ 0x30, 0, 0, 0x00000017 },{ 0x15, 0, 6, 0x00000011 },{ 0x28, 0, 0, 0x00000014 },{ 0x45, 4, 0, 0x00001fff },{ 0xb1, 0, 0, 0x0000000e },{ 0x48, 0, 0, 0x0000000e },{ 0x15, 0, 1, 0x00000035 },{ 0x6, 0, 0, 0x00040000 },{ 0x6, 0, 0, 0x00000000 },};struct sock_fprog bpf {.len (unsigned short) sizeof(code) / sizeof(code[0]),.filter code,};int rv setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, bpf, sizeof(bpf));if (rv 0) {fprintf(stderr, Error: cannot attach BPF filter\n);exit(1);}
}static void print_date(void) {static int day -1;time_t now time(NULL);struct tm *t localtime(now);if (day ! t-tm_yday) {printf(DNS trace for %s, ctime(now));day t-tm_yday;}fflush(0);
}static void run_trace(void) {// grab all Ethernet packets and use a custom BPF filter to get only UDP from source port 53int s socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));if (s 0)errExit(socket);custom_bpf(s);struct timeval tv;tv.tv_sec 10;tv.tv_usec 0;unsigned char buf[MAX_BUF_SIZE];while (1) {fd_set rfds;FD_ZERO(rfds);FD_SET(s, rfds);int rv select(s 1, rfds, NULL, NULL, tv);if (rv 0)errExit(select);else if (rv 0) {print_date();tv.tv_sec 10;tv.tv_usec 0;continue;}unsigned bytes recvfrom(s, buf, MAX_BUF_SIZE, 0, NULL, NULL);if (bytes (14 20 8)) { // size of MAC IP UDP headersuint8_t ip_hlen (buf[14] 0x0f) * 4;uint16_t port_src;memcpy(port_src, buf 14 ip_hlen, 2);port_src ntohs(port_src);uint8_t protocol buf[14 9];uint32_t ip_src;memcpy(ip_src, buf 14 12, 4);ip_src ntohl(ip_src);if (arg_nolocal) {if ((ip_src 0xff000000) 0x7f000000 || // 127.0.0.0/8(ip_src 0xff000000) 0x0a000000 || // 10.0.0.0/8(ip_src 0xffff0000) 0xc0a80000 || // 192.168.0.0/16(ip_src 0xfff00000) 0xac100000) // 172.16.0.0/12continue;}// if DNS packet, extract the queryif (port_src 53 protocol 0x11) // UDP protocolprint_dns(ip_src, buf 14 ip_hlen 8); // IP and UDP header len}}close(s);
}
static const char *const usage_str Usage: fnettrace-dns [OPTIONS]\nOptions:\n --help, -? - this help screen\n --nolocal\n;static void usage(void) {puts(usage_str);
}int main(int argc, char **argv) {int i;for (i 1; i argc; i) {if (strcmp(argv[i], --help) 0 || strcmp(argv[i], -?) 0) {usage();return 0;}else if (strcmp(argv[i], --nolocal) 0)arg_nolocal 1;else {fprintf(stderr, Error: invalid argument\n);return 1;}}if (getuid() ! 0) {fprintf(stderr, Error: you need to be root to run this program\n);return 1;}// kill the process if the parent diedprctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);print_date();run_trace();return 0;
}
这个程序文件是一个用于追踪DNS查询的工具。它使用BPFBerkeley Packet Filter过滤器从网络数据包中提取DNS查询并输出到控制台。以下是对该程序功能的分析 print_dns函数解析并打印DNS查询。它接收源IP地址和指向DNS层的指针作为参数。首先将源IP地址转换为字符串格式。然后检查DNS查询是否有效包括域名长度、类型等。最后根据查询是否为NXDOMAINNon-Existent Domain输出相应的信息。 custom_bpf函数设置自定义BPF过滤器。此过滤器仅捕获来自源端口53的UDP数据包。 print_date函数在每十分钟更新一次日期。 run_trace函数主循环用于读取和处理数据包。首先创建一个原始套接字以捕获所有类型的以太网数据包然后应用自定义的BPF过滤器。接着进入一个无限循环在每次循环中调用select来等待新的数据包到达。当有新数据包时调用recvfrom接收数据包。如果数据包包含DNS查询则调用print_dns进行解析和打印。 main函数程序的入口点。在此函数中处理命令行选项检查当前用户是否具有root权限然后调用print_date和run_trace开始追踪DNS查询。
总之这个程序文件实现了一个简单的DNS查询追踪工具通过捕获网络数据包中的DNS查询并进行解析将其结果显示在控制台上。
print_dns
这段代码是用来解析DNS数据包并打印相关信息的函数。以下是对每一行代码的详细解释 static int arg_nolocal 0;定义一个静态整型变量arg_nolocal并初始化为0。 static char last[512] {\0};定义一个静态字符数组last长度为512所有元素初始化为空字符’\0’。 void print_dns(uint32_t ip_src, unsigned char *pkt)定义一个名为print_dns的函数接受两个参数一个无符号32位整型变量ip_src表示IP源地址和一个无符号字符指针pkt指向DNS层的起始位置。 assert(pkt);检查pkt是否为空指针如果为空则程序会终止运行并输出错误信息。 char ip[30];定义一个字符数组ip长度为30用于存储IP地址的字符串形式。 sprintf(ip, %d.%d.%d.%d, PRINT_IP(ip_src));使用sprintf函数将ip_src转换为点分十进制的IP地址格式并存入ip数组中。 // macro to print ip addresses in a printf statement
#define PRINT_IP(A) \
((int) (((A) 24) 0xFF)), ((int) (((A) 16) 0xFF)), ((int) (((A) 8) 0xFF)), ((int) ( (A) 0xFF)) 定义在common.h中 宏 PRINT_IP(A) 这个宏用于在printf语句中打印IPv4地址。它接受一个32位的无符号整数A代表IPv4地址并通过位移和按位与操作将其拆分为四个8位的字节并将它们转换为int类型后返回。这四个字节按照IPv4地址的格式从高字节到低字节排列。 这个宏 PRINT_IP(A) 的目的是将一个32位的无符号整数代表IPv4地址转换为四个8位的整数并按照IPv4地址的标准格式从高字节到低字节排列。以下是对代码的详细解释 (A) 24这是右移运算符将输入的32位整数A向右移动24位。由于是无符号整数高位补0。这样可以得到最高字节第四个字节的内容。 0xFF这是按位与运算符将上一步的结果与0xFF进行按位与操作。0xFF是一个8位的二进制数每一位都是1这相当于对结果的低8位进行保留高24位清零。这样就可以确保得到的是一个8位的整数。 将上述表达式用 (int) 强制转换为 int 类型确保在printf语句中正确输出。 接下来的三个表达式 (A) 16, (A) 8, (A) 0xFF 分别用于获取IP地址的第三、第二和第一个字节。它们的处理方式与获取第四个字节的方式类似只是右移的位数不同。 最后这些表达式通过逗号运算符 , 连接在一起。在C语言中逗号运算符会计算所有操作数并返回最后一个操作数的值。但在这个宏中我们并不关心返回的值而是利用逗号运算符的副作用——顺序执行各个表达式并将结果作为参数传递给printf函数。 因此当你在printf语句中使用 PRINT_IP(some_ip_address) 时它会被展开为四个8位的整数按照IPv4地址的标准格式排列可以直接用于打印IP地址。例如 uint32_t ip 0x01020304;
printf(IP address: %d.%d.%d.%d\n, PRINT_IP(ip));输出将是 IP address: 1.2.3.4time_t seconds time(NULL);获取当前时间以秒为单位并存储在seconds变量中。 struct tm *t localtime(seconds);将seconds转换为本地时间并存储在一个tm结构体指针t中。 int nxdomain ((*(pkt 3) 0x03) 0x03)? 1: 0;根据DNS数据包中的某个标志位判断是否为NXDOMAIN不存在的域名查询如果是则nxdomain为1否则为0。 这段代码是在处理网络数据包pkt时检查其中的某个字段以确定是否为NXDOMAINNon-Existent Domain不存在的域名响应。 首先它通过指针运算 *(pkt 3) 访问数据包中的第4个字节因为C语言数组下标从0开始计数。 然后使用按位与运算符 将该字节与常量 0x03 进行按位与操作这样可以提取出该字节的最低两位。 接着使用条件运算符 ? : 来判断提取出的最低两位是否等于 0x03 如果等于 0x03则条件为真表达式的结果为 1表示这是一个NXDOMAIN响应。如果不等于 0x03则条件为假表达式的结果为 0表示这不是一个NXDOMAIN响应。 最终这个表达式的值 nxdomain 是一个整数用于表示当前数据包是否为NXDOMAIN响应。如果 nxdomain 等于 1则表示是NXDOMAIN响应如果 nxdomain 等于 0则表示不是NXDOMAIN响应。 使用按位与运算符 将一个字节与常量 0x03 进行操作可以提取出该字节的最低两位这是因为 0x03 的二进制表示为 00000011。 在按位与运算中当两个对应的二进制位都为1时结果位才为1否则为0。因此将任意一个字节与 0x03 进行按位与操作时只有该字节的最低两位与 0x03 的最低两位都是1进行计算其余高位由于与 0x03 的对应位均为0所以结果均为0。 例如假设我们要提取的字节为 0b10101010即十进制的170与 0x03即二进制的 0b00000011进行按位与操作 0b10101010 原始字节0b00000011 常量0x03
-------------------0b00000010 结果可以看到结果中的高位都被清零只剩下原始字节的最低两位。因此通过这种方式我们可以提取出一个字节的最低两位。在这个特定的上下文中这可能用于检查数据包中的特定标志或编码信息。 取出最低两位的具体原因取决于上下文和协议的定义。在不同的网络协议或数据格式中最低两位可能包含特定的标志、状态信息或其他编码数据。 在这个例子中检查最低两位是否等于 0x03 可能是为了识别某种协议的特定响应类型或者错误代码。例如在DNSDomain Name System协议中某些类型的响应可能会在消息中的特定位置设置特定的位模式来表示不同的状态如NXDOMAINNon-Existent Domain。 具体来说如果协议规定了NXDOMAIN响应时某个字节的最低两位应为 0b11即十进制的3十六进制的0x03那么通过按位与运算提取出最低两位并检查是否等于 0x03就可以确定这个响应是否为NXDOMAIN。 因此取出最低两位是为了提取和分析协议中特定的位信息这些信息对于理解数据包的内容和含义至关重要。具体的含义和用法需要参考相关协议或数据格式的规范文档。 在 int nxdomain ((*(pkt 3) 0x03) 0x03)? 1: 0; 这一行代码中*(pkt 3) 是用来访问数据包pkt中的特定字节。 网络数据包通常由多个字节组成这些字节包含了各种协议字段和数据。在这个上下文中pkt 是指向数据包起始位置的指针。 由于C语言中的数组下标是从0开始的所以 *(pkt 3) 实际上是访问数据包中的第4个字节因为3 0 3。选择访问第4个字节的原因可能与特定的网络协议或数据格式有关。 在这个例子中可能是DNSDomain Name System协议或其他相关协议规定了NXDOMAINNon-Existent Domain响应的某个标志或状态信息存储在数据包的第4个字节中的特定位。通过 *(pkt 3) 访问这个字节并使用按位与运算符 与常量 0x03 进行操作可以提取出该字节中的特定位通常是最低两位然后进一步检查这些位是否符合NXDOMAIN响应的特征在这种情况下是否等于 0x03。根据检查结果将 nxdomain 设置为1表示这是一个NXDOMAIN响应否则设置为0表示不是NXDOMAIN响应。 if (pkt[4] ! 0 || pkt[5] ! 1)检查DNS数据包中的问题计数是否为1如果不是则跳转到errout标签。 在DNS数据包中特定字段的位置是根据DNS协议的定义来确定的。通常DNS数据包的结构如下 DNS报文头部12字节 IDIdentification2字节标志Flags2字节问题计数Question Count2字节解答计数Answer Count2字节权威记录计数Authority Count2字节额外记录计数Additional Count2字节 if (pkt[4] ! 0 || pkt[5] ! 1) 这一行代码是在检查DNS数据包中的问题计数Question Count字段。 由于问题计数字段位于DNS报文头部的第4和第5个字节从第0个字节开始计数所以使用下标为4和下标为5来访问这两个字节。 问题计数是一个16位的无符号整数其中低字节存储在第4个字节pkt[4]高字节存储在第5个字节pkt[5]。在这个情况下代码只检查了问题计数是否不为1也就是说它期望问题计数为1。 如果 (pkt[4] ! 0 || pkt[5] ! 1) 条件为真即问题计数不是1注意这里假设pkt[4]应该是0pkt[5]应该是1那么程序会跳转到标记为 errout 的位置进行错误处理。这是因为问题计数为1通常表示一个标准的DNS查询请求如果不是1则可能表示数据包格式有误或者是一个非标准的DNS操作。 unsigned char *ptr pkt 12;定义一个无符号字符指针ptr指向DNS数据包中域名开始的位置。 int len 0;定义一个整型变量len用于记录域名的长度。 while (*ptr ! 0 len 255) { ... }当ptr指向的字符不为0且len小于255时循环处理域名。 if (*ptr 63) { ... }检查域名中的每一个标签即点分隔的部分长度是否超过63如果超过则跳转到errout标签。 int delta *ptr 1;计算当前标签的长度包括标签本身和跟随的null字符。 *ptr .;将当前标签的首字符替换为点号。 len delta;增加len的值表示域名长度增加了delta。 ptr delta;将ptr向前移动delta个字符指向下一个标签。 if (*ptr ! 0) { ... }检查域名末尾是否有一个null字符如果没有则跳转到errout标签。 ptr;将ptr向前移动一个字符指向类型字段。 uint16_t type;定义一个无符号16位整型变量type用于存储DNS查询类型。 memcpy(type, ptr, 2);从ptr指向的位置复制2个字节的数据到type变量中。 type ntohs(type);将type变量中的网络字节序转换为主机字节序。 char tmp[sizeof(last)];定义一个临时字符数组tmp长度与last数组相同。 snprintf(tmp, sizeof(last), %02d:%02d:%02d %-15s DNS %s (type %u)%s, ...);使用snprintf函数将时间、IP地址、域名、类型和NXDOMAIN标记如果适用格式化为一个字符串并存入tmp数组中。 if (strcmp(tmp, last)) { ... }比较tmp和last数组的内容如果不同则执行以下操作。 printf(%s\n, tmp);输出tmp数组中的格式化字符串。 fflush(0);刷新标准输出流。 strcpy(last, tmp);将tmp数组的内容复制到last数组中。 return;结束函数。 errout:标签用于处理错误情况。 printf(%02d:%02d:%02d %15s Error: invalid DNS packet\n, t-tm_hour, t-tm_min, t-tm_sec, ip);输出错误信息包括时间、IP地址和错误描述。 fflush(0);刷新标准输出流。
该函数的主要功能是解析DNS数据包提取出其中的时间、IP地址、域名和查询类型等信息并根据需要输出到屏幕上。同时它还对域名的格式进行了检查确保其符合DNS协议的规定。如果数据包无效或不符合要求则输出错误信息。
custom_bpf
这段代码定义了一个名为custom_bpf的函数其功能是为给定的套接字socket设置一个自定义的Berkeley Packet FilterBPF。这个BPF过滤器的作用是筛选出符合特定条件的IPv4 UDP数据包具体来说源端口为53的数据包通常用于DNS查询。
以下是对每一行代码的详细解释 static void custom_bpf(int sock) {定义一个静态函数custom_bpf接受一个整型参数sock表示要设置BPF过滤器的套接字。 struct sock_filter code[] { ... };定义一个sock_filter结构体数组code其中包含了BPF过滤器的指令集。这些指令用于检查和过滤网络数据包。 sock_filter 是 Linux 内核中伯克利数据包过滤器 (BPF) API 的一个结构。它用于定义网络数据包的个别过滤指令。 sock_filter 结构通常包含以下字段 struct sock_filter {__u16 code; // 过滤指令的操作码__u32 jt; // 条件为真时的跳转目标相对偏移__u32 jf; // 条件为假时的跳转目标相对偏移__u32 k; // 用于指令的操作数
};以下是对每个字段的简要解释 code: 此字段表示过滤指令的操作码或opcode。它指定要在数据包数据上执行的操作类型。 jt 和 jf: 这些字段用于过滤程序中的条件跳转。jt 指定了如果条件为真时要跳转到的相对偏移而 jf 指定了如果条件为假时要跳转到的相对偏移。 k: 此字段是某些过滤指令使用的操作数。其含义取决于正在使用的特定opcode。 一组 sock_filter 结构组合成一个 BPF 程序该程序可以附加到套接字上来根据某些标准过滤入站或出站网络数据包。BPF 虚拟机为每个数据包执行此程序并根据程序执行结果接受或拒绝数据包。 3-20. 这些行定义了BPF过滤器的具体指令。每个指令由四个字节组成分别代表操作码、源寄存器、目标寄存器和立即数。这里是一个简化的指令解释
{ 0x28, 0, 0, 0x0000000c },LOAD_ABSOLUTE将偏移量0xc处的4个字节加载到寄存器A。{ 0x15, 0, 8, 0x00000800 },JEQ等于如果寄存器A的值等于0x0800IPv4头部则跳转到偏移量8处的指令。{ 0x30, 0, 0, 0x00000017 },LOAD_ABSOLUTE将偏移量0x17处的4个字节加载到寄存器A。{ 0x15, 0, 6, 0x00000011 },JEQ等于如果寄存器A的值等于0x0011UDP协议则跳转到偏移量6处的指令。{ 0x28, 0, 0, 0x00000014 },LOAD_ABSOLUTE将偏移量0x14处的4个字节加载到寄存器A。{ 0x45, 4, 0, 0x00001fff },JGT大于如果寄存器A的值大于0x1fffUDP源端口在高位字节则跳转到偏移量0处的指令。{ 0xb1, 0, 0, 0x0000000e },ADD将寄存器A的值加上0xe。{ 0x48, 0, 0, 0x0000000e },LD_W从内存中加载4个字节到寄存器A地址为寄存器A的值加0xe。{ 0x15, 0, 1, 0x00000035 },JEQ等于如果寄存器A的值等于0x0035源端口为53则跳转到偏移量1处的指令。{ 0x6, 0, 0, 0x00040000 },RET返回并允许该数据包通过过滤器。{ 0x6, 0, 0, 0x00000000 },RET返回并阻止该数据包通过过滤器。
struct sock_fprog bpf { ... };定义一个sock_fprog结构体变量bpf用于存储BPF过滤器的信息包括指令集长度和指令集本身。 sock_fprog 是 Linux 内核中另一个与 Berkeley Packet Filter (BPF) 相关的结构。它用于封装一个 BPF 程序并在设置 socket 层的数据包过滤时使用。 sock_fprog 结构通常包含以下字段 struct sock_fprog {unsigned short len; // BPF 程序的指令数struct sock_filter *filter; // 指向 BPF 程序指令数组的指针
};以下是对每个字段的简要解释 len: 此字段表示 BPF 程序中的指令数量。它告诉内核需要执行多少条 BPF 指令来过滤数据包。 filter: 这是一个指向 sock_filter 结构数组的指针。这个数组包含了实际的 BPF 程序指令这些指令将被用来检查和过滤网络数据包。 当需要在套接字级别应用数据包过滤时可以创建一个 sock_fprog 结构实例并通过系统调用如 setsockopt()将其传递给内核。内核然后会使用这个 BPF 程序来决定是否应该允许数据包通过或者丢弃。 这种机制常用于实现轻量级的、在内核中运行的网络数据包过滤例如在防火墙、网络监控工具或者性能分析工具中。 bpf.len (unsigned short) sizeof(code) / sizeof(code[0]),设置bpf结构体中的len字段表示BPF过滤器的指令数量。 bpf.filter code,设置bpf结构体中的filter字段指向BPF过滤器的指令集。 int rv setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, bpf, sizeof(bpf));调用setsockopt系统调用将BPF过滤器附加到给定的套接字上。如果成功返回值rv应大于等于0。
25-27. if (rv 0) { ... }检查setsockopt的返回值如果小于0表示失败则输出错误信息并退出程序。 fprintf(stderr, Error: cannot attach BPF filter\n);向标准错误输出流输出错误信息。 exit(1);终止程序执行并返回1作为退出状态码。 }结束custom_bpf函数的定义。
print_date
这段代码定义了一个名为print_date的静态函数其功能是在每次日期发生变化时打印当前的日期和时间。
以下是对每一行代码的详细解释 static void print_date(void) {定义一个静态函数print_date该函数没有参数和返回值。 static int day -1;定义一个静态整型变量day并初始化为-1。这个变量用于存储前一天的年天数一年中的第几天初始值为-1表示这是程序运行以来的第一次调用。 time_t now time(NULL);调用time函数获取当前的时间以秒为单位并将结果存储在time_t类型的变量now中。 struct tm *t localtime(now);调用localtime函数将now转换为本地时间并将结果存储在一个指向struct tm的指针t中。struct tm包含了日期和时间的各种信息如年、月、日、小时、分钟、秒等。 if (day ! t-tm_yday) { ... }检查当前的年天数t-tm_yday是否与前一天的年天数day不同。如果不同则执行花括号内的代码。 printf(DNS trace for %s, ctime(now));调用printf函数打印一条消息内容为DNS trace for后面跟着由ctime函数生成的日期和时间字符串。ctime函数将time_t类型的变量转换为人类可读的日期和时间字符串。 day t-tm_yday;将当前的年天数t-tm_yday赋值给day变量以便下次调用函数时进行比较。 }结束if语句的花括号。 fflush(0);调用fflush函数刷新标准输出流文件描述符为0通常对应于屏幕。这确保任何缓冲的数据都被立即写入到输出设备。 }结束print_date函数的定义。
总的来说这个函数的作用是在每次日期发生变化时输出一个新的日期和时间行用于标记DNS跟踪的日志记录。通过使用静态变量day来保存前一天的年天数可以避免在同一天内多次打印日期。
run_trace
这段代码定义了一个名为run_trace的静态函数其功能是捕获所有以太网数据包并使用自定义的BPF过滤器筛选出源端口为53的UDP数据包。然后对这些数据包进行解析提取DNS查询信息并打印。
以下是对每一行代码的详细解释 static void run_trace(void) {定义一个静态函数run_trace该函数没有参数和返回值。 // grab all Ethernet packets and use a custom BPF filter to get only UDP from source port 53注释说明了函数的主要功能。 int s socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));创建一个原始套接字用于接收所有类型的以太网数据包。PF_PACKET表示协议家族为packetSOCK_RAW表示原始套接字类型htons(ETH_P_ALL)指定要接收所有以太网协议类型的数据包。 if (s 0) errExit(socket);检查套接字是否成功创建如果小于0则调用errExit函数输出错误信息并退出程序。 custom_bpf(s);调用custom_bpf函数为套接字s设置自定义的BPF过滤器用于筛选源端口为53的UDP数据包。
6-7. struct timeval tv; 和 tv.tv_sec 10; tv.tv_usec 0;定义一个timeval结构体变量tv并设置其tv_sec字段为10秒等待时间tv_usec字段为0微秒。 unsigned char buf[MAX_BUF_SIZE];定义一个无符号字符数组buf用于存储接收到的数据包。 while (1) { ... }无限循环持续接收和处理数据包。
10-12. fd_set rfds; FD_ZERO(rfds); FD_SET(s, rfds);定义一个文件描述符集合rfds清零并添加套接字s到集合中。
int rv select(s 1, rfds, NULL, NULL, tv);调用select函数等待套接字s变为可读状态或者等待超时由tv指定。返回值rv表示就绪的文件描述符数量。
14-15. if (rv 0) errExit(select); else if (rv 0) { ... }检查select的返回值如果小于0则调用errExit函数输出错误信息并退出程序如果等于0则表示超时执行花括号内的代码。 print_date();调用print_date函数打印当前日期和时间。 tv.tv_sec 10; tv.tv_usec 0;重置tv的时间值为10秒。 continue;结束本次循环进入下一轮循环。 unsigned bytes recvfrom(s, buf, MAX_BUF_SIZE, 0, NULL, NULL);从套接字s接收数据包并将数据存入buf中。返回值bytes表示接收到的数据长度。
20-21. if (bytes (14 20 8)) { ... }检查接收到的数据长度是否大于等于MAC头部14字节、IP头部20字节和UDP头部8字节的总长度如果是则继续执行。 uint8_t ip_hlen (buf[14] 0x0f) * 4;根据IP头部中的第一个字节计算IP头部的实际长度并将结果存储在ip_hlen变量中。 uint16_t port_src; memcpy(port_src, buf 14 ip_hlen, 2); port_src ntohs(port_src);从数据包中提取源端口号并将其转换为主机字节序。 uint8_t protocol buf[14 9];从数据包中提取上层协议类型如TCP、UDP等。 uint32_t ip_src; memcpy(ip_src, buf 14 12, 4); ip_src ntohl(ip_src);从数据包中提取源IP地址并将其转换为主机字节序。
26-32. if (arg_nolocal) { ... }如果arg_nolocal标志为真则检查源IP地址是否属于本地网络范围如127.0.0.0/8、10.0.0.0/8、192.168.0.0/16或172.16.0.0/12如果是则跳过本次循环。
33-35. if (port_src 53 protocol 0x11) // UDP protocol print_dns(ip_src, buf 14 ip_hlen 8); // IP and UDP header len如果源端口号为53且上层协议为UDP则调用print_dns函数解析DNS查询信息并打印。 }结束while循环。 close(s);关闭套接字s。 }结束run_trace函数的定义。
main
这段代码是程序的主函数其功能是处理命令行参数、检查运行权限并启动数据包捕获和解析。
以下是对每一行代码的详细解释 int main(int argc, char **argv) {定义主函数main接受两个参数命令行参数的数量argc和指向参数字符串数组的指针argv。 int i;定义一个整型变量i用于遍历命令行参数。
3-5. for (i 1; i argc; i) { ... }使用for循环遍历命令行参数从索引为1的参数开始索引0通常为程序名。
6-8. if (strcmp(argv[i], --help) 0 || strcmp(argv[i], -?) 0) { ... }检查当前参数是否为–help或-?如果是则调用usage函数打印帮助信息并返回0表示程序正常结束。
9-10. else if (strcmp(argv[i], --nolocal) 0) arg_nolocal 1;检查当前参数是否为–nolocal如果是则将全局变量arg_nolocal设置为1表示不显示本地网络范围内的DNS查询。
11-13. else { ... }如果当前参数既不是帮助选项也不是--nolocal则输出错误信息并返回1表示程序异常结束。
}结束else语句。
15-19. if (getuid() ! 0) { ... }检查当前用户的用户ID是否为0即root用户。如果不是则输出错误信息并返回1。 prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);调用prctl系统调用设置进程的父死亡信号为SIGKILL。这意味着当父进程终止时该进程也会被立即杀死。 print_date();调用print_date函数打印当前日期和时间。 run_trace();调用run_trace函数开始捕获和解析数据包。 return 0;返回0表示程序正常结束。 }结束main函数的定义。
补充DNS
DNS协议报文格式 2.1 头部 **会话标识2字节**是DNS报文的ID标识对于请求报文和其对应的应答报文这个字段是相同的通过它可以区分DNS应答报文是哪个请求的响应 标志2字节 [ QR1bit查询/响应标志0为查询1为响应opcode4bit0表示标准查询1表示反向查询2表示服务器状态请求AA1bit表示授权回答TC1bit表示可截断的RD1bit表示期望递归RA1bit表示可用递归rcode4bit表示返回码0表示没有差错3表示名字差错2表示服务器错误Server Failure **数量字段总共8字节**Questions、Answer RRs、Authority RRs、Additional RRs 各自表示后面的四个区域的数目。Questions表示查询问题区域节的数量Answers表示回答区域的数量Authoritative namesversers表示授权区域的数量Additional recoreds表示附加区域的数量
2.2 正文 Queries区域 [ 1.1 查询名 长度不固定且不使用填充字节一般该字段表示的就是需要查询的域名如果是反向查询则为IP反向查询即由IP地址反查域名一般的格式如下图所示。 1.2 查询类型 类型助记符说明1A由域名获得IPv4地址2NS查询域名服务器5CNAME查询规范名称6SOA开始授权11WKS熟知服务12PTR把IP地址转换成域名13HINFO主机信息15MX邮件交换28AAAA由域名获得IPv6地址252AXFR传送整个区的请求255ANY对所有记录的请求这里给一个域名可用来模拟DNS的查询类型可以选择不同的类型比如APTR等玩一下 https://www.nslookuptool.com/chs/ *1.3 查询类通常为1表明是Internet数据* 资源记录(RR)区域包括回答区域授权区域和附加区域
[
该区域有三个但格式都是一样的。这三个区域分别是回答区域授权区域和附加区域
2.1. 域名2字节或不定长它的格式和Queries区域的查询名字字段是一样的。有一点不同就是当报文中域名重复出现的时候该字段使用2个字节的偏移指针来表示。比如在资源记录中域名通常是查询问题部分的域名的重复因此用2字节的指针来表示具体格式是最前面的两个高位是 11用于识别指针。其余的14位从DNS报文的开始处计数从0开始指出该报文中的相应字节数。一个典型的例子C00C(1100000000001100****12正好是头部的长度其正好指向Queries区域的查询名字字段)。
**2.2 查询类型**表明资源纪录的类型见1.2节的查询类型表格所示
**2.3 查询类**对于Internet信息总是IN
**2.4 生存时间TTL**以秒为单位表示的是资源记录的生命周期一般用于当地址解析程序取出资源记录后决定保存及使用缓存数据的时间它同时也可以表明该资源记录的稳定程度极为稳定的信息会被分配一个很大的值比如86400这是一天的秒数。
2.5. 资源数据该字段是一个可变长字段表示按照查询段的要求返回的相关资源记录的数据。可以是Address表明查询报文想要的回应是一个IP地址或者CNAME表明查询报文想要的回应是一个规范主机名等。 报文格式
DNS 一般采用 UDP 作为传输层协议 TCP 亦可端口号 是 53 。请求报文和应答报文均作为数据搭载在 UDP 数据报中进行传输。
DNS 报文分为 请求 和 应答 两种结构是类似的大致分为五部分
头部 header 描述报文类型问题节 question 保存查询问题答案节 answer 保存问题答案也就是查询结果授权信息节 authority 保存授权信息附加信息节 additional 保存附加信息
其中头部是固定的共 12 字节其他节不固定记录数可多可少数目保存在头部中。头部分为 6 个字段
标识 identifier 一个 16 位的 ID 在应答中原样返回以此匹配请求和应答标志 flags 一些标志位共 16 位问题记录数 question count 一个 16 位整数表示问题节中的记录个数答案记录数 answer count 一个 16 位整数表示答案节中的记录个数授权信息记录数 authority record count 一个 16 位整数表示授权信息节中的记录个数附加信息记录数 additional record count 一个 16 位整数表示附加信息节中的记录个数
最后我们来解释一下标志字段中的各个标志位 QR 位标记报文是一个查询请求还是查询应答 0 表示查询请求1 表示查询应答 操作码 opcode 占 4 位表示操作类型 0 代表标准查询1 代表反向查询2 代表服务器状态请求 AA 位表示 权威回答 authoritative answer 意味着当前查询结果是由域名的权威服务器给出的 TC 位表示 截断 truncated 使用 UDP 时如果应答超过 512 字节只返回前 512 个字节此时将自动切换使用 TCP 进行查询。 RD 位表示 期望递归 recursion desired 在请求中设置并在应答中返回 该位为 1 时服务器必须处理这个请求如果服务器没有授权回答它必须替客户端请求其他 DNS 服务器这也是所谓的 递归查询 该位为 0 时如果服务器没有授权回答它就返回一个能够处理该查询的服务器列表给客户端由客户端自己进行 迭代查询 RA 位表示可递归 recursion available 如果服务器支持递归查询就会在应答中设置该位以告知客户端 保留位这 3 位目前未用留作未来扩展 响应码 response code 占 4 位表示请求结果常见的值包括 0 表示没有差错3 表示名字差错该差错由权威服务器返回表示待查询的域名不存在
问题记录
客户端查询域名时需要向服务端发送请求报文待查询域名作为问题记录保存在问题节中。
问题节支持保存多条问题记录记录条数则保存在 DNS 头部中的问题记录数字段。这意味着DNS 协议单个请求能够同时查询多个域名虽然通常只查询一个。
一个问题记录由 3 个字段组成 待查询域名 Name 这个字段长度不固定由具体域名决定 查询类型 Type 域名除了关联 IP 地址还可以关联其他信息常见类型包括 1 表示 A 记录即 IP 地址28 表示 AAAA 记录即 IPv 6 地址Etc… 类 Class 通常为 1 表示 TCP/IP 互联网地址
最后我们回过头来考察域名字段它的长度是不固定的。域名按 . 切分成若干部分再依次保存。每个部分由一个前导计数字节开头记录当前部分的字符数。
以域名 fasionchan.com. 为例以 . 切分成 3 个部分fasionchan 、com 以及空字符串。请注意空字符串代表根域。因此待查询域名字段依次为
一个前导字节保存整数 10 然后 10 个字节保存 fasionchan 部分二级域一个前导字节保存整数 3 然后 3 个字节保存 com 部分一级域一个前导字节保存整数 0 然后 0 个字节保存部分根域
由此可见每一级域名的长度理论上可以支持多达 255 个字符。
资源记录
服务端处理查询请求后需要向客户端发送应答报文域名查询结果作为资源记录保存在答案以及其后两节中。
答案节、授权信息节和附加信息节均由一条或多条资源记录组成记录数目保存在头部中的对应字段不再赘述。
资源记录结构和问题记录非常相似它总共有 6 个字段前 3 个和问题记录完全一样
被查询域名 Name 与问题记录相同查询类型 Type 与问题记录相同类 Class 与问题记录相同有效期 TTL 域名记录一般不会频繁改动所以在有效期内可以将结果缓存起来降低请求频率数据长度 Resource Data Length 即查询结果的长度数据 Resource Data 即查询结果
如果查询类型是 A 记录那查询结果就是一个 IP 地址保存于资源记录中的数据字段而数据长度字段值为 4 因为 IP 地址的长度为 32 位折合 4 字节。
报文实例
以 test.fasionchan.com 这个域名为例来讲解 DNS 查询请求报文和应答报文。
执行 dig 命令即可查询该域名
dig test.fasionchan.com对查询 test.fasionchan.com 的一次通信过程进行抓包结果保存在 Github 上以供参考。童鞋们可以将抓包结果下载到本地并用 WireShark 打开并结合讲解进行分析。
请求报文 抓包结果请求报文只有头部、问题节和附加节图解假设没有附加节。
先看头部问题记录数为 1 其他记录数都是 0 。这意味着请求报文只有问题节而且问题节中只有一条问题记录只查询一个域名。头部中的标志位分别如下
QR0 表示该报文是一个请求报文操作码为 0 表示这个 DNS 请求是一个标准请求TC0 表示请求报文没有被截断RD1 表示客户端希望服务器可以执行递归查询
问题记录我们已经很熟悉了不再赘述
Type1 表示客户端希望查询 A 记录即与域名关联的 IP 地址Class1 代表 TCP/IP 互联网
响应报文
抓包结果应答报文只有头部、问题节和答案节。其中问题节中的问题记录与请求报文一样图中就不展开了。
先看头部问题记录数为 1 答案记录数也是 1 其他记录数都是 0 。这意味着应答报文只有问题节和答案节而且它们各自只有一条记录。头部中的标志位分别如下
QR1 表示该报文是一个应答报文操作码为 0 表示这个 DNS 请求是一个标准请求AA0 表示结果不是由域名的权威服务器返回的因为查询对象是本地的 DNS 缓存服务器如果是向权威服务器发起查询它返回的应答报文 AA1 TC0 表示应答报文没有被截断RD1 与请求报文保持一致略RA1 表示服务端支持递归查询响应码为 0 表示查询成功没有出错
答案节中的资源记录就是查询结果前 3 个字段与问题记录一样不再赘述。
TTL 字段是一个整数表示有效期单位是秒。例子中的查询结果有效期是 752 秒即 12 分 32 秒。也就是说查询结果从现在开始算12 分 32 秒内均有效无须重新请求。
查询结果是一个 IP 地址长度为 4 个字节保存在资源数据字段中。
DNS解析过程
我们平时在访问网站时不使用 IP 地址而是网站域名。但是抓包发现交互报文是以 IP 地址进行的。那么 IP 地址是从哪来的呢这是因为 DNS 把网站域名自动转换为 IP 地址。 DNS 出现
TCP/IP 是基于 IP 地址进行通信的但是 IP 地址不太好记。于是出现了另一种方便记忆的标识符那就是主机名。为计算机配置主机名在进行网络通信时直接使用主机名而不用输入一大串的 IP 地址。同时系统通过一个叫 hosts 的文件实现主机名转换 IP 地址的功能。***hosts 文件包括主机名和 IP 地址的对应关系。***当需要通过主机名访问主机时它就会查看本地的 hosts 文件从文件中找到相对应的 IP 地址然后进行报文发送。如果在 hosts 文件中没找到相关信息则主机访问失败。
hosts 文件是主机的本地文件优点是查找响应速度快。它主要用来存储一些本地网络的主机名和 IP 地址的对应信息。这样主机在以主机名访问本地网络主机时通过 hosts 文件可以迅速获得相应的 IP 地址。 每台主机的 hosts 文件都需要单独手工更新。随着网络规模的不断扩大、接入计算机的数量不断增加维护难度越来越大每台主机同步更新几乎是一件不可能完成的任务。 为了解决 hosts 文件维护困难的问题出现了 DNS 域名系统一个可以解决主机名和 IP 地址互相转换的系统。*无论网络规模变得多么庞大都能在一个小范围内通过 DNS 进行管理。* DNS 介绍
DNS 全称 Domain Name System 。采用 client/server 模式DNS client 发出查询请求DNS server 响应请求。DNS client 通过查询 DNS server 获得主机的 IP 地址进而完成后续的 TCP/IP 通信过程。 当 Windows 系统用户使用 nslookup hostname/domainname 命令时DNS 会自动查找注册了主机名和 IP 地址的数据库并返回对应的 IP 地址。 DNS 域名
先了解什么是域名才能理解 DNS 。*域名是为了识别主机名或机构的一种分层的名称。*因为单独的一台域名服务器是不可能知道所有域名信息所以域名系统是一个分布式数据库系统域名主机名到 IP 地址的解析可以由若干个域名服务器共同完成。每一个站点维护自己的信息数据库并运行一个服务器程序供互联网上的客户端查询。DNS 提供了客户端与服务器的通信协议也提供了服务器之间交换信息的协议。由于是分布式系统即使单个服务器出现故障也不会导致整个系统失效消除了单点故障。
DNS 域名组成
DNS 域的本质是一种管理范围的划分最大的域是根域向下可以划分为顶级域、二级域、三级域、四级域等。相对应的域名是根域名、顶级域名、二级域名、三级域名等。不同等级的域名使用点号分隔级别最低的域名写在最左边而级别最高的域名写在最右边。 举个栗子网站域名 http://www.tsinghua.edu.cn 中从右到左开始cn 是顶级域名代表中国edu是二级域名代表教育机构tsinghua 是三级域名表示清华大学www 则表示三级域名中的主机并提供了 web 服务。 除了 www 主机外常见的主机还有 arch 、sem 、mail 域名如下 每一级的域名都由英文字母和数字组成域名不区分大小写长度不能超过 63 字节一个完整的域名不能超过 255 个字节。根域名用 “ . ”点表示。目前我们看到的域名例子都是完全合格域名 FQDN FQDN 的完整格式是以点结尾的域名。接入互联网的主机、服务器或其它网络设备都可以拥有一个唯一的 FQDN 。与 FQDN 对应的系统中的默认域名是非合格域名会把当前的区域域名添加到尾部。例如tsinghua 域内的主机上查找 mail 本地解析器就会将这个名称转换为 FQDN 即 http://mail.tsinghua.edu.cn 然后解析出 IP 地址。 DNS 域名空间
域名空间结构像是一棵倒过来的树也叫做树形结构。根域名就是树根 root 用点号表示往下是这棵树的各层枝叶。根域名的下一层叫**顶级域名**顶级域名包括三大类 国家顶级域名
国家顶级域名采用 ISO3166 的规定。比如.cn 表示中国.us 表示美国.uk表示英国等。现在使用的国家顶级域名大约在 200个左右。 国际顶级域名
国际顶级域名采用 .int 。国际性的组织可以在 .int 下注册。
通用顶级域名 最早的顶级域名共有 *6* 个。分别为
.com 表示公司企业
.net 表示网络服务机构
.org 表示非盈利组织
.edu 表示教育机构仅限美国
.gov 表示政府部门仅限美国
.mil 表示军事部门仅限美国。
随着互联网用户不断增加又增加了 *7* 个通用顶级域名。分别为
.aero 用于航空运输业
.biz 用于公司和企业
.coop 用于合作团体
.info 用于各种情况
.museum 用于博物馆
.name 用于个人
.pro 用于自由职业者。
顶级域名下面是二级域名。国家顶级域名下注册的二级域名均由国家自行确定。我国二级域名分为类别域名和行政域名两大类类别域名如.com .edu .gov 等分别代表不同的机构行政域名如.bj 表示北京.sh 表示上海代表我国各省、自治区及直辖市等。 二级域名下面是三级域名、四级域名等。*命名树上任何一个节点的域名就是从这个节点到最高层的域名串起来中间以 “ . ” 分隔。* 在域名结构中节点在所属域中的主机名标识可以相同但是*域名必须不同*。比如清华大学和新浪公司下都有一台主机的标识是 mail 但是两者的域名却是不同的前者为 http://mail.tsinghua.edu.cn 而后者为 http://mail.sina.com.cn 。 DNS 域名解析
将域名转换为对应的 IP 地址的过程叫做域名解析。在域名解析过程中DNS client 的主机调用解析器 Resolver 向 DNS server 发出请求DNS server 完成域名解析。 域名解析是按照 DNS 分层结构的特点自顶向下进行的。但是如果每一个域名解析都从根域名服务器开始那么根域名服务器有可能无法承载海量的流量。在实际应用中大多数域名解析都是在本地域名服务器完成。通过合理设置本地域名服务器由本地域名服务器负责大部分的域名解析请求提高域名解析效率。 DNS 解析器
从应用程序的角度看访问 DNS 是通过一个叫**解析器** Resolver 的应用程序来完成的。发送一个 TCP 或 UDP 数据包之前解析器必须将域名主机名转换为 IP 地址。一个解析器至少要注册一个域名服务器的 IP 地址。通常它至少包括本地域名服务器的 IP 地址。 DNS 域名服务器
DNS 域名空间的层次结构允许不同的域名服务器管理域名空间的不同部分。域名服务器是指管理域名的主机及软件它可以管理所在分层的域。其所管理的分层叫做区域 zone 。一个 zone 是 DNS 域名空间的一棵子树它可以单独管理而不受其它 zone 影响。*每层都设有一个域名服务器。* 根所设置的 DNS 叫做根域名服务器它对 DNS 的检索数据功能起着至关重要的作用。根域名服务器中注册了顶级域名服务器的 IP 地址。如果想要新增一个一级域名或者修改已有的顶级域名就要在根域名服务器中进行新增或变更。
类似的顶级域名服务器中注册了二级域名服务器的 IP 地址。如果域名服务器下面没有其它分层就可以自由地指定主机名称。如果想重新设置域名服务器的 IP 地址或修改域名必须在上一层的域名服务器中进行修改。
域名和域名服务器都需要按照分层进行设置。如果域名服务器出现故障那么针对这个域的 DNS 查询就无法正常工作。因此为了提高可用性至少设置两台域名服务器。一旦第一台域名服务器无法提供查询时就会自动转到第二个甚至第三个域名服务器上进行。
DNS 域名服务器类型
本地域名服务器
互联网接入服务运营商或者一个大的网络机构像公司、大学等都有一台或多台可以自行管理的域名服务器这类域名服务器称为**本地域名服务器**也称为默认域名服务器。本地域名服务器离客户端较近。当一个 DNS客户端发出 DNS 查询时首先送到本地域名服务器。如果本地域名服务器数据库中有对应的域名信息会将查询的域名转换为 IP 地址返回客户端。如果没有它会以 DNS 客户端的身份向根域名服务器进行查询。根域名服务器收到本地域名服务器的查询后会返回相关域名服务器的 IP 地址本地域名服务器再向相关域名服务器发送查询请求。 根域名服务器
通常根域名服务器用来管理顶级域本身并不对域名进行解析但它知道相关域名服务器的 IP 地址。IPv4 根域名服务器全球有 13 台主机名分别为 A ~ M 。1 台为主根服务器在美国其余 12 个均为辅根服务器其中 9 个在美国2 个在欧洲位于英国和瑞典1 台在亚洲位于日本。*所有的域名服务器都必须注册根域名服务器的 IP 地址*因为 DNS 根据 IP 地址进行检索时需要按顺序从根域名服务器开始。 授权域名服务器
互联网上的主机在域名服务器上进行注册这个域名服务器就是主机的授权域名服务器。通常主机的授权域名服务器就是本地域名服务器。实际上主机会有两个授权域名服务器防止单点故障。授权域名服务器上有注册主机域名与 IP 地址的映射信息当查询注册主机域名时它会返回相应主机的 IP 地址。如果主机域名和 IP 地址需要进行变更只需要在授权域名服务器处理即可不用再向其它域名服务器进行申请或报告。 主域名服务器
主域名服务器是完成一个或多个区域域名解析工作的主要域名服务器通常也是一个或多个区域的授权域名服务器。主域名服务器有区域内主机地址信息的源数据文件并且是区域传送中区域数据的唯一来源。
辅助域名服务器
辅助域名服务器可以协助主域名服务器提供域名查询服务在主机很多的情况下可以有效分担主域名服务器的压力。辅助域名服务器也有冗余功能当主域名服务器故障时辅助域名服务器能够在数据有效期内继续为主机提供域名解析服务。 一台主域名服务器可以有多台辅助域名服务器一台辅助域名服务器也可以是其它区域的主域名服务器。辅助域名服务器中包含区域内主机地址数据的授权信息通过区域配置文件副本的方式存储。辅助域名服务器也是区域的授权域名服务器可以完成本区域内域名查询的授权回答。
辅助域名服务器并不建立数据文件它获得区域数据的唯一途径就是通过区域传送的方式从主域名服务器上获得区域数据的最新副本。获得数据副本的方式有两种第一种是辅助域名服务器定期主动获得主域名服务器的副本或更新副本中的数据。第二种是主域名服务器在区域数据发生变化时及时通知辅助域名服务器更新副本中的数据。
DNS 域名完整解析过程 DNS 客户端进行域名 http://www.tsinghua.edu.cn 的解析过程如下
DNS 客户端向本地域名服务器发送请求查询 http://www.tsinghua.edu.cn 主机的 IP 地址本地域名服务器查询数据库发现没有域名为 http://www.tsinghua.edu.cn 的主机于是将请求发送给根域名服务器根域名服务器查询数据库发现没有这个主机域名记录但是根域名服务器知道 cn 域名服务器可以解析这个域名于是将 cn 域名服务器的 IP 地址返回给本地域名服务器本地域名服务器向 cn 域名服务器查询 http://www.tsinghua.edu.cn 主机的 IP 地址cn 域名服务器查询数据库也没有相关记录但是知道 http://edu.cn 域名服务器可以解析这个域名于是将 http://edu.cn 域名服务器的 IP 地址返回给本地域名服务器本地域名服务器再向 http://edu.cn 域名服务器查询 http://www.tsinghua.edu.cn 主机 IP 地址http://edu.cn****域名服务器查询数据库也没有相关记录但是知道 http://tsinghua.edu.cn 域名服务器可以解析这个域名于是将 http://tsinghua.edu.cn 的域名服务器 IP 地址返回给本地域名服务器本地域名服务器向 http://tsinghua.edu.cn 域名服务器查询 http://www.tsinghua.edu.cn 主机的 IP 地址http://tsinghua.edu.cn****域名服务器查询数据库发现有主机域名记录于是给本地域名服务器返回 http://www.tsinghua.edu.cn 对应的 IP 地址最后本地域名服务器将 http://www.tsinghua.edu.cn 的 IP 地址返回给客户端整个解析过程完成。
DNS 传输层协议
DNS 域名服务器使用的端口号是 53 并且同时支持 UDP 和 TCP 协议。为什么同时使用两种协议呢
因为 DNS 响应报文中有一个删减标志位用 TC 表示。当响应报文使用 UDP 封装且报文长度大于 512 字节时那么服务器只返回前 512 字节同时 TC 标志位置位表示报文进行了删减。当客户端收到 TC 置位的响应报文后将采用 TCP 封装查询请求。DNS 服务器返回的响应报文长度大于 512 字节。 UDP 报文的最大长度是 512 字节最多可以包含 13 台根域名服务器数据因此 ipv4 根域名服务器只能限制在 13 个且每个服务器使用单个字母命名也是 IPv4 根服务器是从 A ~ M 命名的原因。 当辅助域名服务器启动时将从主域名服务器执行区域传送。正常运行过程中辅助域名服务器也会定时向主域名服务器进行查询以便了解主域名服务器数据是否发送变化。如果有变化将执行一次区域传送。因为区域传送的数据多所以传送的数据采用 TCP 封装。 因此*UDP 用于 client 和 server 的查询和响应**TCP 用于主从 server 之间的传送*。
DNS 查询方式
DNS 域名解析包括两种查询 query 方式一种是递归查询另一种是*迭代查询*。
递归查询
DNS 服务器如果不能直接响应解析请求它将继续请求其它的 DNS 服务器直到查询域名解析的结果。查询的结果可以是域名主机的 IP 地址或者是域名无法解析。*无论哪种结果DNS 服务器都会将结果返回给客户端。*
举个栗子当本地域名服务器接收了客户端的查询请求本地域名服务器将代表客户端来找答案而在本地域名服务器执行工作时客户端只是等待直到本地域名服务器将最终查询结果返回客户端。 迭代查询
如果 DNS 服务器查不到相应记录会向客户端返回一个可能知道结果的域名服务器 IP 地址由客户端继续向新的服务器发送查询请求。对域名服务器的迭代查询只得到一个提示则继续查询。
举个栗子本地域名服务器发送请求到根域名服务器根域名服务器并没有相应记录它只是给本地域名服务器返回一个提示引导本地域名服务器到另一台域名服务器进行查询。 客户端在查询 IP 地址时向本地域名服务器进行递归查询。如果本地域名服务器的数据库有相应数据则直接返回相应数据。如果没有则本地域名服务器向根域名服务器进行迭代查询。从根开始对这棵树按照顺序进行遍历直到找到指定的域名服务器并由这个域名服务器返回相应的数据。*客户端和本地域名服务器会将收到的信息保存在缓存里*这样可以减少每次查询时的性能消耗。
DNS 反向查询
在 DNS 查询中客户端希望知道域名对应的 IP 地址这种查询称为**正向查询。大部分的 DNS 查询都是正向查询。与正向查询对应的是反向查询**。它允许 DNS 客户端通过 IP 地址查找对应的域名。 为实现反向查询在 DNS 标准中定义了特色域 in-addr.arpa 域并保留在域名空间中以便执行反向查询。为创建反向域名空间in-addr.arpa 域中的子域是按照 IP 地址相反的顺序构造的。
举个栗子http://www.tsinghua.edu.cn 的 IP 地址是 166.111.4.100 那么在 in-addr.arpa 域中对应的节点就是 100.4.111.166 。 DNS 应用场景
静态域名解析
域名解析分为动态域名解析和静态域名解析。在解析域名时首先采用静态域名解析如果静态解析不成功再采用动态域名解析。
静态域名解析是通过静态域名解析表进行的手动建立域名和 IP 地址之间的对应关系表该表的作用类似于 Windows 操作系统下的 hosts 文件可以将一些常用的域名放入表中。当 DNS client 需要域名所对应的 IP 地址时即到静态域名解析表中去查找指定的域名从而获得所对应的 IP 地址提高域名解析的效率。 动态域名解析
**动态域名解析**需要专用的域名服务器 DNS server 运行域名解析服务器程序提供从域名到 IP 地址的映射关系负责接收客户端 DNS client提出的域名解析请求。 DNS 代理
在使用了 DNS 代理 DNS proxy 功能的组网中DNS client 将 DNS 请求报文直接发送给 DNS proxy 。DNS proxy 会先查找本地域名解析表如果未查询到对应的解析表项会将 DNS 请求报文转发给 DNS Server 并在收到 DNS server 的应答报文后将其返回给 DNS client 从而实现域名解析。 因此当 DNS server 的地址发生变化时只需改变 DNS proxy 上的配置无需逐一改变局域网内每个 DNS client 的配置从而简化了网络管理。
一些常用的域名放入表中。当 DNS client 需要域名所对应的 IP 地址时即到静态域名解析表中去查找指定的域名从而获得所对应的 IP 地址提高域名解析的效率。
[外链图片转存中…(img-rh2a8Jjh-1702973915144)]
动态域名解析
**动态域名解析**需要专用的域名服务器 DNS server 运行域名解析服务器程序提供从域名到 IP 地址的映射关系负责接收客户端 DNS client提出的域名解析请求。
[外链图片转存中…(img-idZCzhYN-1702973915144)]
DNS 代理
在使用了 DNS 代理 DNS proxy 功能的组网中DNS client 将 DNS 请求报文直接发送给 DNS proxy 。DNS proxy 会先查找本地域名解析表如果未查询到对应的解析表项会将 DNS 请求报文转发给 DNS Server 并在收到 DNS server 的应答报文后将其返回给 DNS client 从而实现域名解析。
[外链图片转存中…(img-FHfGNhK8-1702973915145)]
因此当 DNS server 的地址发生变化时只需改变 DNS proxy 上的配置无需逐一改变局域网内每个 DNS client 的配置从而简化了网络管理。