当前位置:网站首页>C语言实现DNS请求器
C语言实现DNS请求器
2022-06-24 19:19:00 【qq_42120843】
C语言实现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请求和响应报文


技术要点
- 使用传输层的UDP Socket进行编程
- 应用层的DNS报文格式
程序执行流程

程序设计流程
- DNS请求头以及正文部分的结构体定义
- DNS header 数据填充
- DNS question 数据填充
- 合并DNS header和question部分(build_requestion)
- 通过UDP Socket 发送请求报文和接受响应报文
- 解析出响应报文的相关数据
代码剖析
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 ID、Flags、Questions.其中会话标识随机生成,标志和问题数要进行主机字节序到网络字节序的转换:
//将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 -> request
memcpy(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编程的基本流程和函数调用顺序基本相同,
代码流程:
- 创建UDP Socket
- 填充服务器地址结构体的数据(struct sockaddr_in)
- 连接以保证尽可能的可靠(connect)
- DNS报文数据填充 (dns_create_header、dns_build_requestion)
- 通过socket发送DNS请求报文(sendto)
- 接受DNS响应报文(recvfrom)
- 解析响应报文
int dns_client_commit(const char *domain)
{
//下方流程是基本定死的套路
//1.创建UDP socket
//网络层ipv4, 传输层用udp
int 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); //一个域名可能对应多个ip
ptr += 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转为点分十进制地址存到ip
inet_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 = 0
while(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 0000
return ((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 -> request
memcpy(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 0000
return ((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); //一个域名可能对应多个ip
ptr += 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转为点分十进制地址存到ip
inet_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, 传输层用udp
int 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;
}
编译和执行

问题
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字段。
边栏推荐
- Several common command operations in win system
- RFC 793 why to send reset and when to send reset
- [cloud native learning notes] kubernetes Foundation
- Static routing job
- Auto. JS to realize automatic unlocking screen
- Summary of idea practical skills: how to rename a project or module to completely solve all the problems you encounter that do not work. It is suggested that the five-star collection be your daughter
- Postman assertion
- Sleep revolution - find the right length of rest
- OSI and tcp/ip model
- A/B测试助力游戏业务增长
猜你喜欢

It was Tencent who jumped out of the job with 26k. It really wiped my ass with sandpaper. It gave me a hand

Oauth1.0 introduction

Microsoft Certification (dynamic 365) test

Please open online PDF carefully

Pytest test framework II

大厂出海,败于“姿态”

Network flow 24 questions (round table questions)

Limit summary (under update)

Failed to open after installing Charles without any prompt

EditText 控制软键盘出现 搜索
随机推荐
Basic database syntax learning
PHP script calls command to get real-time output
Power apps Guide
Network security review office starts network security review on HowNet
DHCP operation
Golang daily question
Common self realization functions in C language development
Arkit与Character Creator动画曲线的对接
Analysis of tcpdump packet capturing kernel code
List set Introduction & common methods
自己总结的wireshark抓包技巧
[cloud native learning notes] deploy applications through yaml files
After idea installs these plug-ins, the code can be written to heaven. My little sister also has to arrange it
Requests requests for web page garbled code resolution
Background operation retry gave up; KeeperErrorCode = ConnectionLoss
Learn together and make progress together. Welcome to exchange
Dijkstra seeking secondary short circuit (easy to understand)
Am, FM, PM modulation technology
Pod lifecycle in kubernetes
Role of wait function