DNS-bind9 框架功能和测试方式总结

DNS

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

域名结构

i采用了层次结构命名。每个域名都是一个标号序列(labels),用字母,数字,连接符组成。它用点号分割成一个个标号,每个标号可以看成是一个个的层级,级别低的写在左边,级别高的写在右边,域名服务主要基于UDP实现。

顶级域  ----    com   edu     gov     net
第二级域----    baidu    163
第三级域----    www     gitlab

域名解析过程

在有了域名结构之后需要有机器对其进行解析,而不同层级的根据高低级别由根域名服务器,顶级域名服务器,权限域名服务器,本地域名服务器。域名解析过程分为两大步骤:第一个步骤是本机向本地域名服务器发出一个DNS请求报文,报文携带要查询的域名,第二个步骤是本地域名服务器向本机回应一个DNS响应报文,包含域名对应的IP地址。
以百度为例具体流程描述如下:

  1. 主机 192.168.11.123 先向本地域名服务器 192.168.1.2 进行递归查询
  2. 本地域名服务器采用迭代查询,向一个根域名服务器进行查询
  3. 根域名服务器告诉本地域名服务器,下一次应该查询的顶级域名服务器 baidu.com 的IP 地址
  4. 本地域名服务器向顶级域名服务器 baidu.com 进行查询
  5. 顶级域名服务器 .com 告诉本地域名服务器,下一步查询权限服务器 www.baidu.com的 IP 地址
  6. 本地域名服务器向权限服务器 www.baidu.com 进行查询
  7. 权限服务器 www.baidu.com 告诉本地域名服务器所查询的主机的 IP 地址
  8. 本地域名服务器最后把查询结果告诉 122.152.222.180

递归查询:本机向本地域名服务器发出一次查询请求就静待最终的结果。如果本地域名服务器无法解析,自己会以 DNS 客户机的身份向其它域名服务器查询,直到得到最
终的 IP 地址告诉本机

迭代查询本地域名服务器向根域名服务器查询,根域名服务器告诉它下一步到哪里去查询,然后它再去查,每次它都是以客户机的身份去各个服务器查询

权威DNS服务器和递归DNS服务器

权威DNS服务器和递归DNS服务器在域名解析过程中的工作原理如下:

1、权威DNS服务器工作原理:

  • 接收到查询请求后,检查自身是否具有请求域名的解析信息

  • 如果有,则直接返回相应的IP地址或其他记录信息。

  • 如果没有,则返回“域名不存在”或者转发请求到其他权威DNS服务器。

2、递归DNS服务器工作原理:

  • 接收到查询请求后,从根域名服务器开始逐级向下查询

  • 依次向辖域名服务器、顶级域名服务器、次级域名服务器等发送查询请求,直到找到包含所请求域名解析信息的权威DNS服务器。

  • 将解析结果返回给发起查询请求的设备或其他DNS服务器。

协议报文格式

请添加图片描述
报文分为头部和正文:

头部

会话标识(2 字节):是 DNS 报文的 ID 标识,请求和应答是相同的。

标志(2字节):
请添加图片描述

QR(1bit)查询/响应标志,0 为查询,1 为响应
opcode(4bit)0 表示标准查询,1 表示反向查询,2 表示服务器状态请求
AA(1bit)表示授权回答
TC(1bit)表示可截断的
RD(1bit)表示期望递归
RA(1bit)表示可用递归
rcode(4bit)表示返回码,0 表示没有差错,3 表示名字差错,2 表示服务器错误(Server Failure)
数量字段(总共 8 字节):Questions、Answer RRs、Authority RRs、Additional RRs 各自表示后面的四个区域的数目。

正文

Queries
请添加图片描述

查询名:长度不固定,不填充字节,一般表示反向查询,即由IP地址反查域名格式如下

查询类:通常为1,表明internet数据
原纪录(RR):包括回答区域,授权区域和附加区域


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

#define DNS_SERVER_PORT		53
#define DNS_SERVER_IP		"114.114.114.114"

#define DNS_HOST			0x01
#define DNS_CNAME			0x05

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;
};

struct dns_item{
	char *domain;
	char *ip;
};

//client send to dns server

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;
	
}

//hostname: www.baidu.com
//www
//baidu
//com

//name: 3www5baidu3com0

int dns_creat_questions(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 -2;
	}
	
	question->length = strlen(hostname) + 2;

	question->qtype = htons(1);
	question->qclass = htons(1);

	//name
	const char delim[2] = ".";
	char *qname = question->name;
	
	char *hostname_dup = strdup(hostname);	//strdup --> malloc
	char *token = strtok(hostname_dup,delim);	//www.0voice.com

	while(token != NULL){

		size_t len = strlen(token);

		*qname = len;
		qname++;

		strncpy(qname, token, len+1);
		qname += len;

		token = strtok(NULL, delim);
	}

	free(hostname_dup);
	
}

//

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 --> server	
	memcpy(request,header, sizeof(struct dns_header));
	int 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;
}

static int is_pointer(int in) {
	return ((in & 0xC0) == 0xC0);
}

static void dns_parse_name(unsigned char *chunk, unsigned char *ptr,char *out,int *len){

	int flag = 0, n = 0, alen = 0;
	char *pos = out + (*len);

	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 {

			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 dns_parse_response(char *buffer, struct dns_item **domains){

	int i = 0;
	unsigned char *ptr = buffer;

	ptr += 4;
	int querys = ntohs(*(unsigned short*)ptr);

	ptr += 2;
	int answers = ntohs(*(unsigned short*)ptr);

	ptr += 6;
	for(i = 0;i< querys; i ++){
	while(i){
		int flag = (int)ptr[0];
		ptr += (flag + 1);
		if(flag = 0) break;

	}
	ptr += 4;	

	}

	char cname[128], aname[128], ip[20], netip[4];
	int len, type, ttl, datalen;

	int cnt = 0;
	struct dns_item *list = (struct dns_item*)calloc(answers, sizeof(struct dns_item));
	if(list == NULL){
		return -1;
	}

	for(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;
			dns_parse_name(buffer, ptr, cname, &len);
			ptr += datalen;
			
		} else if (type == DNS_HOST) {

			bzero(ip, sizeof(ip));

			if (datalen == 4) {
				memcpy(netip, ptr, datalen);
				inet_ntop(AF_INET , netip , ip , sizeof(struct sockaddr));

				printf("%s has address %s\n" , aname, ip);
				printf("\tTime 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;
	

}


int dns_client_commit(const char *domain){

	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);

	int ret = connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
	printf("connect: %d\n", ret);
	
	struct dns_header header = {0};
	dns_create_header(&header);

	struct dns_question question = {0};	
	dns_creat_questions(&question, domain);

	char request[1024] = {0};
	int length = dns_build_request(&header, &question, request, 1024);

	//requst
	int slen = sendto(sockfd, request, length, 0, (struct sockaddr*)&servaddr, sizeof(struct sockaddr));
	printf("requst: %d\n", slen);
	//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\n", n);

	struct dns_item *dns_domain = NULL;
	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]);
}

rndc

bind软件安装后,会产生几个固有文件,分为两类:一类是配置文件在/etc目录下,一类是dns记录文件在/var/named目录下。

yum install -y bind bind-chroot bind-utils
bind:bind的主程序软件包,进程名为named
bind-chroot:为bind提供chroot功能,将bind进程限制在自己的家目录下,防止错误的权限设置影响到整个系统。
bind-utils:提供一些工具。如dig
 

1. 安装BIND 9:
首先,确保你的系统上安装了BIND 9 DNS服务器。在大多数Linux发行版中,你可以使用包管理器(如yum、apt或dnf)来安装。
sudo yum install bind bind-utils
2. 生成rndc密钥:
使用rndc-confgen工具生成一个新的rndc密钥文件。
sudo rndc-confgen -a -b 127.0.0.1
这个命令会在/etc/目录下生成rndc.key文件,并在/etc/named.conf中自动添加密钥配置。
3. 配置rndc:
编辑/etc/rndc.conf文件,确保controls部分包含允许控制DNS服务器的IP地址和密钥。例如:
conf
controls {  
    inet 127.0.0.1 port 953  
            allow { 127.0.0.1; };  
            keys { "rndc-key"; };  
};
这里的127.0.0.1是允许控制服务器的IP地址,你可以根据需要修改它。"rndc-key"是之前生成的密钥名称。
4. 配置named.conf:
确保/etc/named.conf中的options部分包含allow-query指令,以允许客户端查询。同时,检查是否包含了之前由rndc-confgen自动添加的密钥配置。
5. 重启BIND服务:
为了使配置生效,需要重启BIND服务。
sudo systemctl restart named
测试rndc:
使用rndc status命令来测试rndc是否可以成功连接到DNS服务器。如果一切正常,你应该会看到关于DNS服务器状态的信息。

配置防火墙:
如果你的服务器启用了防火墙,确保允许rndc使用的端口(默认为953)的入站连接。例如,在使用firewalld的情况下:
sudo firewall-cmd --permanent --add-port=953/tcp  
sudo firewall-cmd --reload
完成以上步骤后,你就应该成功地配置了rndc来控制你的BIND 9 DNS服务器了。

bind软件安装后,会产生几个固有文件,分为两类:

一类是配置文件在/etc目录下,一类是dns记录文件在/var/named目录下。

yum install -y  bind bind-chroot bind-utils
bind:bind的主程序软件包,进程名为named
bind-chroot:chroot功能,将bind进程限制在自己的家目录下,防止错误权限影响到整个系统。
bind-utils:提供一些工具。如 dig 测试工具:
 

bind9日志记录

BIND支持丰富的日志记录,并且支持将日志信息写入文件和发送到syslog中。
1. BIND日志记录的重要的概念。

通道channel
用于指定日志数据的流向,如syslog、文件
类别categories
用于指定记录哪些日志,如查询日志queries、动态更新日志update、解析器的递归查询处理日志resolver等。
每个类别的日志数据可以指定发送到一个或者多个不同的通道channels中。
通道允许根据日志消息的严重级别来过滤输出指定级别的日志,安装严重性递减的顺序排列是critical–>error–>warning–>notice–>info–>debug [level]–>dynamic
前面5个就是syslog所使用的严重级别,后面两个是BIND特有的。

logging {
        channel query_log {
                file "/usr/local/bind/log/query.log." versions 5 size 50m;
                print-time yes;
                severity info;
        };
        category queries { query_log;};
        channel general_log {
                file "/usr/local/bind/log/general.log." versions 5 size 50m;
                print-time yes;
                print-category yes;
                print-severity yes;
                severity info;
        };
        category default   { general_log; };
        category general   { general_log; };
};

配置文件中通过channel定义了2个日志,日志数据流向均是服务器本地的文件中,通过file自语句来指定。channel的名称是根据情况自己定义。
query_log这个channel中通过category语句指定了日志的类别是queries即查询日志,而general_log这个channel通过category语句指定了default和general两种类别的日志均输入到这个channel中。通过category设置类别的语法可见,如果需要多个类别那么则写多个即可,类别后面的花括号指定这类日志归属那个channel。
print-time代表在日志数据中打印时间戳信息,通常如果日志流向syslog则不需要这个设置,因为syslog有也记录日期和时间。
print-category代表日志数据中打印类别的名称。
print-severity yes代表日志数据中打印级别。
severity 日志级别。
version选项用于指定文件保存多少备份版本(几个文件回滚),如果设置为unlimited则代表没有限制,此时bind将保留99个版本文件。如设置为3,那么BIND将保留file、file0、file1、file2共4个文件,每次文件的轮转顺序是把file1改为file2,把file0改为file1把file改为file0,然后新建一个file。
size选项用于限制日志文件的增长,K代表kilobytes、M代表megabytes、G代表gigabytes。

常用的日志类别说明如下:

类别名称    类别描述
default    匹配所有未明确分配通道的类别,bind9的default不匹配未分类的BIND消息
general    所有未明确分类的BIND消息
config    配置文件的分析与处理
client    对客户端请求的处理
network    网络操作
notify    异步区域变更通知
queries    查询日志
update    动态更新日志
query-errors    关于域名请求失败的消息
resolver    递归查询处理
xfer-in    服务器接受区传送消息
xfer-out    服务器发出的区传送消息
dnssec    DNSSEC和TSIG协议处理消息

  • 29
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值