C语言实现DNS请求器

文章目录

  • C语言实现DNS请求器
    • 项目介绍
    • 前置知识
      • DNS介绍
      • DNS的分层
      • 域名解析
      • 递归查询和迭代查询
      • DNS协议报文格式
        • 头部(Header)
        • Queries(查询问题区域)
      • wireshark 分析DNS请求和响应报文
    • 技术要点
    • 程序执行流程
    • 程序设计流程
    • 代码剖析
      • DNS header和 question部分结构体实现
      • DNS header数据填充
      • DNS Queries部分数据的填充
      • 合并DNS header和question部分
      • UDP Socket编程发送DNS请求和接收DNS响应
      • 解析出响应报文的相关数据
        • 解析响应的函数
        • 解析出域名的函数
    • 完整源代码
    • 问题

项目介绍

本程序完成给定指定域名请求对应ip地址的功能,类似于win和linux下nslookup的功能

前置知识

DNS介绍

域名系统(英文:Domain Name System ,缩写 DNS )是互联网的一项服务。它作为将域名和 IP 地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。 DNS 使用 TCP 和 UDP 端口 53 。当前,对于每一级域名长度的限制是 63 个字符,域名总长度则不能超过 253 个字符。域名系统(英文:Domain Name System ,缩写 DNS 的作用是将人类可读的域名如, www.example.com) 转换为机器可读的 IP 地址 如, 192.0.2.44) 。

DNS的分层

域名系统是分层次的。
在域名系统的层次结构中,各种域名都隶属于域名系统根域的下级。域名的第一级是顶级域,它包括通用顶级域,例如 .com 、 .net 和 .org ;以及国家和地区顶级域,例如 .us 、 .cn 和 .tk 。顶级域名下一层是二级域名,一级一级地往下。这些域名向人们提供注册服务,人们可以用它创建公开的互联网资源或运行网站。顶级域名的管理服务由对应的 域名注册 管理机构(域名注册局)负责,注册服务通常由域名注册商负责。

域名解析

主机名到IP 地址的映射有两种方式:
静态映射 在本机上配置域名和 IP 的映射,旨在本机上使用。 Windows 和 Linux的 hosts 文件中的内容就属于静态映射。

动态映射 建立一套域名解析系统( DNS ),只在专门的 DNS 服务器上配置主机到IP 地址的映射,网络上需要使用主机名通信的设备,首先需要到 DNS 服务器查询主机所对应的 IP 地址。通过域名去查询域名服务器,得到IP 地址的过程叫做域名解析。在解析域名时,一般先静态域名解析,再动态解析域名。可以将一些常用的域名放入静态域名解析表中,这样可以大大提高域名解析效率。

递归查询和迭代查询

  • 递归查询:本机向本地域名服务器发出一次查询请求,就静待最终的结果。如果本地域名服务器无法解析,自己会以 DNS 客户机的身份向其它域名服务器查询,直到得到最终的 IP 地址告诉本机。
  • 迭代查询:本地域名服务器向根域名服务器查询,**根域名服务器告诉它下一步到哪里去查询,然后它再去查,**每次它都是以客户机的 身份去各个服务器查询

DNS协议报文格式

头部(Header)

Queries(查询问题区域)

域名( 2 字节或不定长): 它的格式和 Queries 区域的查询名字字段是一样的。有一点
不同就是,当报文中域名重复出现的时候,该字段使用 2 个字节的偏移指针来表示。比
如,在资源记录中,域名通常是查询问题部分的域名的重复,因此用 2 字节的指针来表
示,具体格式是最前面的两个高位是 11(0xC0) ,用于识别指针。其余的 14 位从 DNS 报文的开
始处计数(从 0 开始),指出该报文中的相应字节数。一个典型的例子,
C00C(11 00 000000001100, 12 (字节)正好是头部的长度,其正好指向 Queries 区域的查询名字字段 。
查询类型(Type): 表明资源纪录的类型.
查询类(Class): 对于 Internet 信息,总是 IN
生存时间( TTL 以秒为单位,表示的是资源记录的生命周期,一般用于当地址解析程
序取出资源记录后决定保存及使用缓存数据的时间,它同时也可以表明该资源记录的稳
定程度,极为稳定的信息会 被分配一个很大的值(比如 86400 ,这是一天的秒数)。
资源数据: 该字段是一个可变长字段,表示按照查询段的要求返回的相关资源记录的数
据。**可以是 Address (表明查询报文想要的回应是一个 IP 地址)**或者 CNAME (表明查询
**报文想要的回应是一个规范主机名)**等。

wireshark 分析DNS请求和响应报文

技术要点

  1. 使用传输层的UDP Socket进行编程
  2. 应用层的DNS报文格式

程序执行流程

程序设计流程

  1. DNS请求头以及正文部分的结构体定义
  2. DNS header 数据填充
  3. DNS question 数据填充
  4. 合并DNS header和question部分(build_requestion)
  5. 通过UDP Socket 发送请求报文和接受响应报文
  6. 解析出响应报文的相关数据

代码剖析

DNS header和 question部分结构体实现

对照DNS报文格式进行实现即可

//dns报文Header部分数据结构
struct dns_header{unsigned short id; //2字节(16位)unsigned short flags; unsigned short questions; //问题数unsigned short answer; //回答数unsigned short authority;unsigned short additional;
};//dns报文Queries部分的数据结构
struct dns_question{int length; //主机名的长度,自己添加的,便于操作unsigned short qtype;unsigned short qclass;//查询名是长度和域名组合//如www.0voice.com ==> 60voice3com0//之所以这么做是因为域名查询一般是树结构查询,com顶级域,0voice二级域unsigned char *name; // 主机名(长度不确定)
};//dns响应报文中数据(域名和ip)的结构体
struct dns_item{char *domain; char *ip;
};

DNS header数据填充

我们只填充3个字段:Transaction IDFlagsQuestions.其中会话标识随机生成,标志和问题数要进行主机字节序到网络字节序的转换:

//将header部分字段填充数据
int dns_create_header(struct dns_header *header)
{if(header == NULL)return -1;memset(header, 0x00, sizeof(struct dns_header));//id用随机数,种子用time(NULL),表明生成随机数的范围srandom(time(NULL)); // 线程不安全header->id = random();//网络字节序(大端):地址低位存数据高位;主机字节序则与之相反//主机(host)字节序转网络(net)字节序header->flags = htons(0x0100);header->questions = htons(1);return 0;
}

DNS Queries部分数据的填充

对照报文中Queries的部分进行填充

int dns_create_question(struct dns_question *question, const char *hostname)
{if(question == NULL || hostname == NULL)return -1;memset(question, 0x00, sizeof(struct dns_question));//内存空间长度:hostname长度 + 结尾\0 再多给一个空间question->name = (char *)malloc(strlen(hostname) + 2);if(question->name == NULL){return -2;}question->length = strlen(hostname) + 2;//查询类型1表示获得IPv4地址question->qtype = htons(1);//查询类1表示Internet数据question->qclass = htons(1);//【重要步骤】//名字存储:www.0voice.com -> 3www60voice3com const char delim[2] = ".";char *qname = question->name; //用于填充内容用的指针//strdup先开辟大小与hostname同的内存,然后将hostname的字符拷贝到开辟的内存上char *hostname_dup = strdup(hostname); //复制字符串,调用malloc//将按照delim分割出字符串数组,返回第一个字符串char *token = strtok(hostname_dup, delim);while(token != NULL){//strlen返回字符串长度不含'\0'size_t len = strlen(token);*qname = len;//长度的ASCII码qname++;//将token所指的字符串复制到qname所指的内存上,最多复制len + 1长度 //len+1使得最后一个字符串把\0复制进去strncpy(qname, token, len + 1);qname += len;//固定写法,此时内部会获得下一个分割出的字符串返回(strtok会依赖上一次运行的结果)token = strtok(NULL, delim); //依赖上一次的结果,线程不安全}free(hostname_dup);
}

合并DNS header和question部分

将header部分数据和question部分数据合并到字符数组中去。

//将header和question合并到request中
//header [in]
//question [in]
//request [out]
//rlen:代表request的大小
int dns_build_requestion(struct dns_header *header, struct dns_question *question, char *request, int rlen)
{if (header == NULL || question == NULL || request == NULL)return -1;memset(request, 0, rlen);//header -> requestmemcpy(request, header, sizeof(struct dns_header));int offset = sizeof(struct dns_header);//Queries部分字段写入到request中,question->length是question->name的长度memcpy(request + offset, question->name, question->length);offset += question->length;memcpy(request + offset, &question->qclass, sizeof(question->qclass));offset += sizeof(question->qclass);memcpy(request + offset, &question->qtype, sizeof(question->qtype));offset += sizeof(question->qtype);return offset; //返回request数据的实际长度
}

UDP Socket编程发送DNS请求和接收DNS响应

UDP Socket编程的基本流程和函数调用顺序基本相同,

代码流程:

  1. 创建UDP Socket
  2. 填充服务器地址结构体的数据struct sockaddr_in
  3. 连接以保证尽可能的可靠(connect
  4. DNS报文数据填充 (dns_create_header、dns_build_requestion)
  5. 通过socket发送DNS请求报文(sendto)
  6. 接受DNS响应报文(recvfrom)
  7. 解析响应报文
int dns_client_commit(const char *domain)
{//下方流程是基本定死的套路//1.创建UDP socket//网络层ipv4, 传输层用udpint sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){return -1;}//2.结构体填充数据struct sockaddr_in servaddr;bzero(&servaddr, sizeof(servaddr)); //将结构体数组清空servaddr.sin_family = AF_INET; servaddr.sin_port = htons(DNS_SERVER_PORT);//点分十进制地址转为网络所用的二进制数 替换inet_pton//servaddr.sin_addr.s_addr = inet_addr(DNS_SERVER_IP);inet_pton(AF_INET, DNS_SERVER_IP, &servaddr.sin_addr.s_addr);//UDP不一定要connect,只是这样提高成功发送请求的可能性connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));//3.dns报文的数据填充struct dns_header header = {0};dns_create_header(&header);struct dns_question question = {0};dns_create_question(&question, domain);char request[1024] = {0};int len = dns_build_requestion(&header, &question, request, 1024);//4.通过sockfd发送DNS请求报文int slen = sendto(sockfd, request, len, 0, (struct sockaddr *)&servaddr, sizeof(struct sockaddr));char response[1024] = {0};struct sockaddr_in addr;size_t addr_len = sizeof(struct sockaddr_in);//5.接受DNS服务器的响应报文//addr和addr_len是输出参数int n = recvfrom(sockfd, response, sizeof(response), 0, (struct sockaddr *)&addr, (socklen_t *)&addr_len);struct dns_item *dns_domain = NULL;//6.解析响应dns_parse_response(response, &dns_domain);free(dns_domain);return n; //返回接受到的响应报文的长度
}

解析出响应报文的相关数据

解析响应的函数

//struct dns_item** 是因为当struct dns_item *为NULL时需要改变其值
static int dns_parse_response(char *buffer, struct dns_item **domains)
{int i = 0;unsigned char *ptr = buffer;ptr += 4; // ptr向前4字节,指向Questions(问题数)字段开头int querys = ntohs(*(unsigned short *)ptr);ptr += 2; //ptr向前2字节,指向Answer RR回答数开头int answers = ntohs(*(unsigned short *)ptr); //一个域名可能对应多个ipptr += 6; //ptr向前6字节,指向Queries(查询问题区域)Name字段的开头for(i = 0;i < querys; i++){//如查询的网址为www.0voice,则Name = 3www60voice3com0 while(1){//flag就是随后字符串的长度int flag = (int)ptr[0];ptr += (flag + 1); if(flag == 0)   break;}ptr += 4; //指向下一个查询名Name字段的开头//最后一次循环是跳过Type和Class字段}char cname[128], aname[128], ip[20], netip[4];int len, type, ttl, datalen;int cnt = 0;//动态分配内存的数组//分配answers个dns_item的内存,并全部置为0,返回指向一个位置的指针struct dns_item *list = calloc(answers, sizeof(struct dns_item));if(list == NULL){return -1;}//解析出Answers(回答区域)的内容for(int i = 0;i < answers;++i){bzero(aname, sizeof(aname));len = 0;//从buffer中ptr指向的位置解析出域名到aname中,并将长度写入len中dns_parse_name(buffer, ptr, aname, &len);ptr += 2; //???type = htons(*(unsigned short *)ptr);ptr += 4;ttl = htons(*(unsigned short *)ptr);ptr += 4;datalen = ntohs(*(unsigned short *)ptr);ptr += 2;if(type == DNS_CNAME){bzero(cname, sizeof(cname));len = 0;从buffer的ptr位置开始解析出内容到cname中,len用来接受解析出的内容长度dns_parse_name(buffer, ptr, cname, &len);ptr += datalen;}else if(type == DNS_HOST){bzero(ip, sizeof(ip));//ipv4为4字节if(datalen == 4){memcpy(netip, ptr, datalen);//二进制网络字节序netip转为点分十进制地址存到ipinet_ntop(AF_INET, netip, ip, sizeof(struct sockaddr));printf("%s has address %s\n", aname, ip);printf("\t Time to live : %d minutes, %d seconds\n",ttl / 60, ttl % 60);list[cnt].domain = (char *)calloc(strlen(aname) + 1, 1);memcpy(list[cnt].domain, aname, strlen(aname));list[cnt].ip = (char *)calloc(strlen(ip) + 1, 1);memcpy(list[cnt].ip, ip, strlen(ip));cnt++;}ptr += datalen;}}*domains = list;ptr += 2; //????return cnt;
}

解析出域名的函数

域名( 2 字节或不定长): 它的格式和 Queries 区域的查询名字字段是一样的。有一点
不同就是,当报文中域名重复出现的时候,该字段使用 2 个字节的偏移指针来表示。比
如,在资源记录中,域名通常是查询问题部分的域名的重复,因此用 2 字节的指针来表
示,具体格式是最前面的两个高位是 11(0xC0) ,用于识别指针。其余的 14 位从 DNS 报文的开
始处计数(从 0 开始),指出该报文中的相应字节数。一个典型的例子,
C00C(11 00 000000001100, 12 (字节)正好是头部的长度,其正好指向 Queries 区域的查询名字字段 。

第一次出现某个Name(域名)

第二次出现相同的域名,只占两个字节(C0 0C = 1100 0000 0000 1100),高2位表示是指针,低14位表示这个域名第一次出现的偏移(12字节)

//从chunk的ptr指向的位置开始解析名字,长度写入len
static void dns_parse_name(unsigned char* chunk, unsigned char *ptr, char *out, int *len)
{int flag = 0, n = 0, alen = 0;//pos指向的内存用于存储解析得到的结果char *pos = out + (*len); // 传入的 *len = 0while(1){flag = (int)ptr[0]; if(flag == 0) break;//如果为指针表明该Name重复出现过,这一字段只占2字节if(is_pointer(flag)){n = (int)ptr[1]; //获取第一次Name出现的偏移ptr = chunk + n;dns_parse_name(chunk, ptr, out, len);break;}else //不是指针,表明是第一次出现Name的地方,此时flag表示随后字符串长度{ptr++;memcpy(pos, ptr, flag);pos += flag;ptr += flag;*len += flag;if((int)ptr[0] != 0){memcpy(pos, ".", 1);pos += 1;(*len) += 1;}}}}static int is_pointer(int in)
{//0xC0 : 1100 0000return ((in & 0xC0) == 0xC0);
}

完整源代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <time.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define DNS_SERVER_PORT     53
#define DNS_SERVER_IP       "114.114.114.114"#define DNS_HOST            0x01
#define DNS_CNAME           0x05//dns报文Header部分数据结构
struct dns_header{unsigned short id; //2字节(16位)unsigned short flags; unsigned short questions; //问题数unsigned short answer; //回答数unsigned short authority;unsigned short additional;
};//dns报文Queries部分的数据结构
struct dns_question{int length; //主机名的长度,自己添加的,便于操作unsigned short qtype;unsigned short qclass;//查询名是长度和域名组合//如www.0voice.com ==> 60voice3com0//之所以这么做是因为域名查询一般是树结构查询,com顶级域,0voice二级域unsigned char *name; // 主机名(长度不确定)
};//dns响应报文中数据(域名和ip)的结构体
struct dns_item{char *domain; char *ip;
};//将header部分字段填充数据
int dns_create_header(struct dns_header *header)
{if(header == NULL)return -1;memset(header, 0x00, sizeof(struct dns_header));//id用随机数,种子用time(NULL),表明生成随机数的范围srandom(time(NULL)); // 线程不安全header->id = random();//网络字节序(大端)地址低位存数据高位//主机(host)字节序转网络(net)字节序header->flags = htons(0x0100);header->questions = htons(1);return 0;
}//将Queries部分的字段填充数据
int dns_create_question(struct dns_question *question, const char *hostname)
{if(question == NULL || hostname == NULL)return -1;memset(question, 0x00, sizeof(struct dns_question));//内存空间长度:hostname长度 + 结尾\0 再多给一个空间question->name = (char *)malloc(strlen(hostname) + 2);if(question->name == NULL){return -2;}question->length = strlen(hostname) + 2;//查询类型1表示获得IPv4地址question->qtype = htons(1);//查询类1表示Internet数据question->qclass = htons(1);//【重要步骤】//名字存储:www.0voice.com -> 3www60voice3com const char delim[2] = ".";char *qname = question->name; //用于填充内容用的指针//strdup先开辟大小与hostname同的内存,然后将hostname的字符拷贝到开辟的内存上char *hostname_dup = strdup(hostname); //复制字符串,调用malloc//将按照delim分割出字符串数组,返回第一个字符串char *token = strtok(hostname_dup, delim);while(token != NULL){//strlen返回字符串长度不含'\0'size_t len = strlen(token);*qname = len;//长度的ASCII码qname++;//将token所指的字符串复制到qname所指的内存上,最多复制len + 1长度 //len+1使得最后一个字符串把\0复制进去strncpy(qname, token, len + 1);qname += len;//固定写法,此时内部会获得下一个分割出的字符串返回(strtok会依赖上一次运行的结果)token = strtok(NULL, delim); //依赖上一次的结果,线程不安全}free(hostname_dup);
}//将header和question合并到request中
//request是传入传出参数
int dns_build_requestion(struct dns_header *header, struct dns_question *question, char *request, int rlen)
{if (header == NULL || question == NULL || request == NULL)return -1;memset(request, 0, rlen);//header -> requestmemcpy(request, header, sizeof(struct dns_header));int offset = sizeof(struct dns_header);//Queries部分字段写入到request中,question->length是question->name的长度memcpy(request + offset, question->name, question->length);offset += question->length;memcpy(request + offset, &question->qclass, sizeof(question->qclass));offset += sizeof(question->qclass);memcpy(request + offset, &question->qtype, sizeof(question->qtype));offset += sizeof(question->qtype);return offset; //返回request数据的实际长度
}static int is_pointer(int in)
{//0xC0 : 1100 0000return ((in & 0xC0) == 0xC0);
}//从chunk的ptr指向的位置开始解析名字,长度写入len
static void dns_parse_name(unsigned char* chunk, unsigned char *ptr, char *out, int *len)
{int flag = 0, n = 0, alen = 0;//pos指向的内存用于存储解析得到的结果char *pos = out + (*len); // 传入的 *len = 0//???while(1){flag = (int)ptr[0]; // ???if(flag == 0) break;if(is_pointer(flag)){n = (int)ptr[1];ptr = chunk + n;dns_parse_name(chunk, ptr, out, len);break;}else //不是指针,表明是第一次出现Name的地方{ptr++;memcpy(pos, ptr, flag);pos += flag;ptr += flag;*len += flag;if((int)ptr[0] != 0){memcpy(pos, ".", 1);pos += 1;(*len) += 1;}}}}//解析响应
//struct dns_item** 是因为当struct dns_item *为NULL时需要改变其值
static int dns_parse_response(char *buffer, struct dns_item **domains)
{int i = 0;unsigned char *ptr = buffer;ptr += 4; // ptr向前4字节,指向Questions(问题数)字段开头int querys = ntohs(*(unsigned short *)ptr);ptr += 2; //ptr向前2字节,指向Answer RR回答数开头int answers = ntohs(*(unsigned short *)ptr); //一个域名可能对应多个ipptr += 6; //ptr向前6字节,指向Queries(查询问题区域)Name字段的开头for(i = 0;i < querys; i++){//如查询的网址为www.0voice,则Name = 3www60voice3com0 while(1){int flag = (int)ptr[0];ptr += (flag + 1); //???if(flag == 0)   break;}ptr += 4; //指向下一个查询名Name字段的开头或跳过Type和Class字段}char cname[128], aname[128], ip[20], netip[4];int len, type, ttl, datalen;int cnt = 0;//动态分配内存的数组//分配answers个dns_item的内存,并全部置为0,返回指向一个位置的指针struct dns_item *list = calloc(answers, sizeof(struct dns_item));if(list == NULL){return -1;}for(int i = 0;i < answers;++i){bzero(aname, sizeof(aname));len = 0;//解析出域名dns_parse_name(buffer, ptr, aname, &len);ptr += 2;type = htons(*(unsigned short *)ptr);ptr += 4;ttl = htons(*(unsigned short *)ptr);ptr += 4;datalen = ntohs(*(unsigned short *)ptr);ptr += 2;if(type == DNS_CNAME){bzero(cname, sizeof(cname));len = 0;//猜:从buffer的ptr位置开始解析出内容到cname中,len用来接受解析出的内容长度dns_parse_name(buffer, ptr, cname, &len);ptr += datalen;}else if(type == DNS_HOST){bzero(ip, sizeof(ip));//ipv4为4字节if(datalen == 4){memcpy(netip, ptr, datalen);//二进制网络字节序netip转为点分十进制地址存到ipinet_ntop(AF_INET, netip, ip, sizeof(struct sockaddr));printf("%s has address %s\n", aname, ip);printf("\t Time to live : %d minutes, %d seconds\n",ttl / 60, ttl % 60);list[cnt].domain = (char *)calloc(strlen(aname) + 1, 1);memcpy(list[cnt].domain, aname, strlen(aname));list[cnt].ip = (char *)calloc(strlen(ip) + 1, 1);memcpy(list[cnt].ip, ip, strlen(ip));cnt++;}ptr += datalen;}}*domains = list;ptr += 2; // 经测试这行不加也行return cnt;
}//客户端向dns服务器发送请求
int dns_client_commit(const char *domain)
{//下方流程是基本定死的套路//1.创建UDP socket//网络层ipv4, 传输层用udpint sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){return -1;}//2.结构体填充数据struct sockaddr_in servaddr;bzero(&servaddr, sizeof(servaddr)); //将结构体数组清空servaddr.sin_family = AF_INET; servaddr.sin_port = htons(DNS_SERVER_PORT);//点分十进制地址转为网络所用的二进制数 替换inet_pton//servaddr.sin_addr.s_addr = inet_addr(DNS_SERVER_IP);inet_pton(AF_INET, DNS_SERVER_IP, &servaddr.sin_addr.s_addr);//UDP不一定要connect,只是这样提高成功发送请求的可能性connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));//3.dns报文的数据填充struct dns_header header = {0};dns_create_header(&header);struct dns_question question = {0};dns_create_question(&question, domain);char request[1024] = {0};int len = dns_build_requestion(&header, &question, request, 1024);//4.通过sockfd发送DNS请求报文int slen = sendto(sockfd, request, len, 0, (struct sockaddr *)&servaddr, sizeof(struct sockaddr));char response[1024] = {0};struct sockaddr_in addr;size_t addr_len = sizeof(struct sockaddr_in);//5.接受DNS服务器的响应报文//addr和addr_len是输出参数int n = recvfrom(sockfd, response, sizeof(response), 0, (struct sockaddr *)&addr, (socklen_t *)&addr_len);struct dns_item *dns_domain = NULL;//6.解析响应dns_parse_response(response, &dns_domain);free(dns_domain);return n; //返回接受到的响应报文的长度
}int main(int argc, char *argv[])
{if(argc < 2) return -1;dns_client_commit(argv[1]);return 0;
}

编译和执行

问题

  1. dns_parse_response函数中下面部分

    for(int i = 0;i < answers;++i){bzero(aname, sizeof(aname));len = 0;//解析出域名dns_parse_name(buffer, ptr, aname, &len);ptr += 2;// ???????为什么ptr指针只要移动2个字节,而不是len,aname难道不是可变的吗,只有跳过aname实际长度才会到Type字段。
    

C语言实现DNS请求器相关推荐

  1. UDP编程-DNS解析器的分析与实现(C语言)

    基本知识 基本介绍 域名系统(英文:Domain Name System,缩写:DNS)的作用是将人类可读的域名 (如,www.example.com) 转换为机器可读的 IP 地址 (如,192.0 ...

  2. DIY一个DNS查询器:了解DNS协议

    每当我们在浏览器上敲入任何一个域名访问某个网站的时候,我们都要使用Dns协议进行一次"域名:IP"的查询;作为命令行使用者,与dns有关用的最多的就是Nslookup 命令吧:作为 ...

  3. Go语言写的解析器(支持json,linq,sql,net,http等)

    Monkey程序语言 Monkey v2.0版本已发布. monkey v2.0 增加了如下内容: 新增 short arrow(->)支持(类似C#的lambda表达式) 增加 列表推导和哈希 ...

  4. 在“DNS管理器”中手工增加DNS主机(A)或者别名(CNAME)记录时,出现被拒绝的错误...

    问题现象: AD域控制器操作系统为Win2008R2,在"DNS管理器"中手工增加DNS主机(A)或者别名(CNAME)记录时,出现被拒绝的错误.但是将客户端加入域后,在" ...

  5. 技术报告:APT组织Wekby利用DNS请求作为CC设施,攻击美国秘密机构

    技术报告:APT组织Wekby利用DNS请求作为C&C设施,攻击美国秘密机构 最近几周Paloalto Networks的研究人员注意到,APT组织Wekby对美国的部分秘密机构展开了一次攻击 ...

  6. WireShark抓DNS请求和回复数据报的分析

    1 DNS简单理解 我们简单理解DNS功能是把域名转成IP地址,我们先发送一个NDS请求数据包到本地域名服务器去找,找不到我们就去根域名服务器去找,根域名找不到我们再把顶级域名服务器地址回复给本地域名 ...

  7. 爱好-C语言秘钥产生器

    爱好-C语言秘钥产生器 重回C语言学习.从基础看起,突发奇想写一些感兴趣的小程序. 还是先上代码: #include<stdio.h> #include<stdlib.h> # ...

  8. 纯前端语言编写音乐播放器

    纯前端语言编写音乐播放器 html代码 index.html <!DOCTYPE html> <html lang="en"><head>< ...

  9. 主機名稱控制者: DNS 伺服器

    http://linux.vbird.org/linux_server/0350dns.php#Lame_server 第十九章.主機名稱控制者: DNS 伺服器 切換解析度為 800x600 最近更 ...

最新文章

  1. 4.3.6 无分类编址CIDR
  2. NYOJ2—括号配对问题
  3. 评测通知 | 2022年全国知识图谱与语义计算大会评测任务发布
  4. python读取文件最后几行_如何用python获取文件的最后一行,文件可能会比较大
  5. 线性表之--队列操作
  6. 大数据之-Hadoop3.x_HDFS_数据完整性_HDFS的CRC数据校验---大数据之hadoop3.x工作笔记0078
  7. vue - 自定义指令
  8. 获取Android手机短信中心号码
  9. 嵌入式C语言入门操作
  10. 西门子模块选择pdf_西门子S120变频器编码器模块的选择和配置
  11. mysql蠕虫复制原理_mysql蠕虫复制基础知识点
  12. Welcome to MySQL Workbench:MySQL 复制表
  13. 互联网寒冬?软件测试行业饱和了?为何每年还会增加40万测试员?
  14. java实现图片的压缩且保留图片尺寸不变
  15. python画花的代码怎么打不开_怎么用python画玫瑰花,求大神贴代码,感激不尽
  16. Bash 单行注释与多行注释
  17. 六张图片形成立方体并旋转
  18. Android自定义IM聊天界面
  19. 【iOS15更新必学】 如何完整备份iPhone资料?
  20. 在pycharm中%matplotlib inline报错!!!

热门文章

  1. 编辑pdf - pdfplumber的使用
  2. 掌握函数urlopen()的用法
  3. 记程序员兼职打打小怪以来的体验
  4. 连接Oracle时出现ORA-12505错误
  5. 前端工程师常考手写面试题指南
  6. CSS3 greyscale 实现元素转换成黑白色(灰色、置灰)
  7. Excel如何查找批注
  8. 解决git下载很慢的问题
  9. 前员工抄袭自腾讯?如今要被老东家告到破产!
  10. Doctype作用?