DNS
Domain Name System 域名系统,是一个分布式数据库,用于映射IP与域名
每级域名长度限制为63,总长度限制为253,使用TCP UDP端口53
DNS分层
顶级域:com org等 第二级域:baidu google等 第三级域:www edu等
域名解析
静态映射:在本机上配置域名和ip映射并直接使用
动态映射:使用DNS域名解析系统,在DNS服务器上配置ip到域名的映射
域名服务器
根域名服务器: 共a-m十三台(十三个ip)但拥有很多镜像服务器,镜像与本体使用同一个ip,存有顶级域名服务器的ip 顶级域名服务器:管理在该顶级域名服务器下注册的二级域名 权限域名服务器:一个区域的域名解析 本地域名服务器:处理本地的请求,保存本地的映射
域名解析方式
迭代查询:本机请求本地域名服务器,本地域名服务器开始迭代的查询各个层级服务器,先查询根获得顶级的ip然后根据获得的ip查询顶级在获得区域的ip依次迭代查到请求的映射
递归查询:递归查询时只发出一次请求然后等待接收到最终结果,在上面的步骤中本机使用的就是递归查询
协议报文格式
具体查看文档
DNS client UDP编程
首先需要自己定义数据结构用于存储dns报文
struct dns_header{
unsigned short id;
unsigned short flags;
unsigned short questions;
unsigned short answer;
unsigned short authority;
unsigned short additional;
};
struct dns_question {
int length;
unsigned short qtype;
unsigned short qclass;
unsigned char *name;
};
这里只需要question和header是因为我们作为client只实现发送A请求也就是获取域名的ipv4地址,在实现中header的授权码和附加码都不需要使用只需要使用questions id和flags即可
先建立UDP套接字用于发送UDP报文:
#define DNS_SERVER_PORT 53
#define DNS_SERVER_IP "114.114.114.114"
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
return -1;
}
struct sockaddr_in servaddr = {0};
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(DNS_SERVER_PORT);
servaddr.sin_addr.s_addr = inet_addr(DNS_SERVER_IP);
然后使用connect检测开辟本机到DNS服务器的通路:
int ret = connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
ret返回值可以用于检测通路是否存在
然后开始初始化要发送的DNS报文:
1.初始化dns header:
struct dns_header header = {0};
dns_create_header(&header);
使用的dns_create_header:
int dns_create_header(struct dns_header *header)
{
if(header == NULL) return -1;
memset(header, 0, sizeof(struct dns_header));
//random
srandom(time(NULL));
header->id = random();
header->flags = htons(0x0100);
header->questions = htons(1);
return 0;
}
这里的id为随机生成 questions为1代表一个问题 flags的0x0100查表后代表:期望递归 传地址来初始化header
然后就是初始化正文部分的question:
struct dns_question question = {0};
dns_create_question(&question, domain);
dns_create_question的实现:
int dns_create_question(struct dns_question* question, const char *hostname)
{
if(question == NULL || hostname == NULL) return -1;
memset(question, 0, sizeof(struct dns_question));
question->name = (char*)malloc(strlen(hostname) + 2);
if(question->name == NULL)
{
return -1;
}
question->length = strlen(hostname) + 2;
question->qtype = htons(1);
question->qclass = htons(1);
//name
const char delim[2] = ".";
char *hostname_dup = strdup(hostname);
char *token = strtok(hostname_dup, delim);
char *qname = question->name;
while(token != NULL)
{
size_t len = strlen(token);
*qname = len;
qname++;
strncpy(qname, token, len+1); // +1 copy \0
qname += len;
token = strtok(NULL, delim);
}
free(hostname_dup);
}
这里正文的初始化使用循环和strtok分别取出每一级域名然后形成正文部分,www.baidu.com –> 3www5baidu3com0
接下来就可以将已经初始化好的header和question组装成dns报文request:
char request[1024] = {0};
int length = dns_build_request(&header, &question, request, 1024);
dns_build_request的实现:
int dns_build_request(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
int offset = 0;
memcpy(request, header, sizeof(struct dns_header));
offset = sizeof(struct dns_header);
//question --> request
memcpy(request+offset, question->name, question->length);
offset += question->length;
memcpy(request+offset, &question->qtype, sizeof(question->qtype));
offset += sizeof(question->qtype);
memcpy(request+offset, &question->qclass, sizeof(question->qclass));
offset += sizeof(question->qclass);
return offset;
}
可以看到就是按照协议进行拼接
接下来就可以调用unix 网络编程capi来通过udp套接字发送正文:
//request
int slen = sendto(sockfd, request, length, 0, (struct sockaddr*)&servaddr, sizeof(struct sockaddr));
至此发送就结束了
我们发送了dns查询报文dns服务器会返回结果,所以可以通过套接字来接收这个结果,并打印出来验证一下:
//recvfrom()
char response[1024] = {0};
struct sockaddr_in addr;
size_t addr_len = sizeof(struct sockaddr_in);
int n =recvfrom(sockfd, response, sizeof(response), 0, (struct sockaddr*)&addr, (socklen_t*)&addr_len);
printf("recvfrom : %d, %s\n", n, response);
也可以写代码解析返回的结果,具体查看代码就可以
至此就实现了一个dns客户端用于给dns服务器发送查询报文来查询一个域名的ipv4地址