一文看懂本地搭建DNS及配置
概述
DNS全称Domain Name SystemDNS简介因IP地址不便于记忆,但域名便于记忆,为了更方便地访问互联网,需将域名和IP地址进行映射,DNS就是用于维护这种映射关系的系统,部署有DNS的服务器叫DNS服务器。提出者保罗·莫卡派乔斯首次实现时间1984年主流DNS工具BIND、PowerDNS、Unbound、CoreDNS、MaraDNS相关地址ICANN、RFC882
1 windows下搭建DNS
windows下搭建DNS可以使用DNS管理器(仅windows server操作系统支持),也可以借助第三方工具(如:BIND、 MaraDNS或Unbound),本文以windows server 2019为例。
使用DNS管理器时,本机IP必须使用静态IP地址。
1.1 安装 DNS管理器
以管理员身份运行 PowerShell, 执行如下命令:
Install-WindowsFeature -Name DNS -IncludeManagementTools
执行如下命令确认安装成功:
Get-WindowsFeature -Name DNS
安装完成后,在运行窗口中输入: dnsmgmt.msc,即可打开DNS管理器
1.2 正向查找区域
正向查找区域:通过域名来查找相应 IP地址的区域。
1.2.1 添加正向查找区域
依次执行如下步骤:
1.2.2 添加域名解析记录
常见的域名解析记录有以下几种:
A 记录:用于将域名映射到 IPv4 地址。
AAAA 记录:用于将域名映射到 IPv6 地址。
CNAME 记录:用于将一个域名指向另一个域名,形成别名。
MX 记录:用于指向邮件服务器。
若存在多条域名相同但IP地址不同的记录,解析该域名时会返回所有IP地址。
这里以添加A记录为例:
添加成功后,能看到如下映射关系:
启用nslookup(Name Server Lookup)查看解析情况:
在域名服务器上查看
在本地查看
其中:10.56.223.139为DNS服务器的IP地址,若解析时不想带上DNS服务器IP地址,可在网络设置中手动配置DNS服务器地址,如下所示:
配置成功后,再次解析可不带上DNS服务器的IP地址,如下所示:
当客户端启用了VPN时,仍然需要带上DNS服务器IP地址,因为VPN中配置的DNS优先级更高。
1.3 反向查找区域
反向查找区域:通过IP 地址来查找相应域名的区域。
1.3.1 添加反向查找区域
依次执行如下步骤:
反向查找区域的网络ID为1个网段
1.3.2 添加PTR记录
PTR (Pointer Record)即指针记录,主要用于将具体的IP地址反向解析为域名
若存在多条IP地址相同但域名不同的PTR记录,反向解析该IP地址时会返回第一个域名或随机返回其中一个域名。
添加PTR记录可参考如下步骤:
添加完成后能够看到1条PTR记录
利用nslookup命令查看反向解析结果
1.3.3 设置DNS服务器名称
前面返回的服务器名称均为Unknown,是由于未设置DNS服务器名称,设置DNS服务器名称可参考如下步骤:
先根据DNS服务器的IP地址创建反向查找区域
再添加DNS服务器本身的域名解析记录,添加时勾选“创建相关的指针(PTR)记录”,这样将自动创建DNS服务器的PTR记录
再次利用nslookup命令查看反向解析结果,能够看到DNS服务器的名称:
1.4 转发器
转发器本身也是DNS服务器,配置转发器后,当本地DNS服务器无法解析时,会自动转到配置的转发器进行解析。
未配置转发器时,默认会自动转发到根服务器进行解析
配置了转发器时,可设置当转发器也无法解析时,转发到根服务器进行解析
配置转发器可参考如下步骤:
配置转发器后,不会立即生效,需要按如下方式清理DNS缓存:
通过nslookup命令进行解析:
dcom148.cedi.com这个域名在DNS服务器本身中没有解析记录,但是在转发器(10.56.223.11)中有解析记录,因此也能够正常解析。
移除转发器后,再次查看解析:
虽然还是能够解析,但是IP地址不一样(该IP是由根服务器解析出来的,因为未配置转发器时会默认自动由根服务器进行解析)
1.5 根提示
根提示(Root Hints)是一个 DNS 配置文件,包含了根服务器(Root Servers)的 IP 地址列表,,通常由 DNS 服务器提供,当未配置转发器时,本地DNS服务器无法解析时,会默认自动根据根提示中的根服务器进行解析。
查看根提示中的根服务器:
根服务器分为IPv4根服务器和IPv6根服务器,上图中根服务器为IPv4根服务器,IPv4根服务器域名于1995年注册,并于1997年搭建完成。
IPv4根服务器一共13台,这13台均为逻辑根服务器(由 A 到 M,由美国、英国、日本和瑞典4个共同运营,其中A为主根服务器,其余的为辅根服务器,辅根服务器会定期从主根服务器进行同步),这些逻辑根服务器实际上是多台物理服务器的集合,每台逻辑根服务器有多个分布在世界各地的实例,一共约有1000多个实例,中国境内部署有几十个根服务器实例。
2015年6月,由中国下一代互联网工程中心领衔发起,联合WIDE机构(现国际互联网M根运营者)、互联网域名工程中心(ZDNS)等共同创立了“雪人计划”,旨在全球范围内架设IPv6根服务器(IPv4地址已于2011年由IANA在全球5大RIR机构分配完毕,截止2024年,仅非洲地区剩余少量IPv4地址未分配给ISP),其中1台主根和3台辅根部署在中国。
2 linux下搭建DNS
linux下搭建DNS也可以借助多种第三方工具,各主流DNS搭建工具对比如下:
工具名称优势使用场景BIND功能最强大、应用最广泛大型、复杂的 DNS 配置PowerDNS数据库驱动、API 支持动态管理大量 DNS 数据dnsmasq轻量级、简单易用小型局域网、嵌入式设备Unbound高性能递归查询缓存 DNS 或递归查询Knot DNS高性能、多核优化高流量权威 DNSCoreDNS插件化、现代架构Kubernetes 或微服务环境MaraDNS轻量级、高安全性小型网络或嵌入式环境
下面演示在centos 7下借助BIND工具搭建DNS服务器的完整流程,完整脚本见本文开头处。
2.1 安装
#!/bin/bash
# 日志文件(后面才定义,在此之前不记录日志)
LOG_FILE="/dev/null"
# 打印提示信息并写入日志
function log_info(){
content="[INFO] $(date '+%Y-%m-%d %H:%M:%S') $@"
echo $content >> $LOG_FILE && echo -e "\033[32m" ${content} "\033[0m"
}
# 打印警告信息并写入日志
function log_warn(){
content="[WARN] $(date '+%Y-%m-%d %H:%M:%S') $@"
echo $content >> $LOG_FILE && echo -e "\033[33m" ${content} "\033[0m"
}
# 打印错误信息并写入日志
function log_err(){
content="[ERROR] $(date '+%Y-%m-%d %H:%M:%S') $@"
echo $content >> $LOG_FILE && echo -e "\033[31m" ${content} "\033[0m"
}
# 使用说明
usage() {
echo "脚本用途:"
echo -e "\033[32m搭建DNS服务\033[0m"
echo -e "\n参数:"
echo -e "\033[32m共8个,分别为:安装目录、主域名、DNS服务器IP、DNS服务器自身三级域名、递归查询(yes/no)、转发器地址、管理员邮箱地址、定时更新区域文件频率(分钟)"
echo -e "\n示例:"
echo -e "\033[32m$0 /opt/dns testDNS.org 10.56.223.15 dnsServer15 yes 223.5.5.5 admin@163.com 10\033[0m"
exit 1
}
# 如果传入参数数量不正确,则显示使用说明
if [ $# -ne 8 ]; then
usage
fi
# 解析传入参数
INSTALL_DIR=$1
PRIMARY_DOMAIN=$2
DNS_IP=$3
SUB_DOMAIN=$4
RECURSION=$5
FORWARDERS=$6
ADMIN_EMAIL=$7
UPDATE_TIME=$8
# 打印解析后的参数
echo -e "\033[32m"----------------------------"\033[0m"
echo "安装目录:$INSTALL_DIR"
echo "主域名:$PRIMARY_DOMAIN"
echo "DNS服务器IP:$DNS_IP"
echo "DNS服务器自身三级域名:$SUB_DOMAIN"
echo "是否开启根提示:$RECURSION"
echo "转发器地址:$FORWARDERS"
echo "管理员邮箱地址:$ADMIN_EMAIL"
echo "定时更新记录频率(分钟):$UPDATE_TIME"
echo -e "\033[32m"----------------------------"\033[0m"
# 邮箱地址合法性校验
if ! [[ $ADMIN_EMAIL =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
log_err "管理员邮箱地址不合法:$ADMIN_EMAIL"
exit 1
else
#将邮箱中的 @ 符号替换为 .,以便于添加到区域文件中
ADMIN_EMAIL=$(echo "$ADMIN_EMAIL" | tr '@' '.')
fi
# 定时更新记录频率合法性校验
if ! [[ "$UPDATE_TIME" =~ ^[0-9]+$ ]]; then
log_err "更新记录频率参数必须是一个非负整数(单位:分钟)!"
exit 1
fi
# 检查目录是否存在
if [ ! -d "$INSTALL_DIR" ]; then
log_info "创建安装目录..."
mkdir -p "$INSTALL_DIR" || { log_info "创建安装目录失败!"; exit 1; }
# 将更新区域文件的脚本放入安装目录中
log_info "将更新区域文件的脚本update_zone.sh移动到安装目录中..."
mv update_zone.sh $INSTALL_DIR
else
log_err "安装目录已存在,请重新指定目录..."
exit 1
fi
#################创建相关变量############
# 提取服务器IP的前三段并反转,用于反向查找区域
IP_PREFIX=$(echo $DNS_IP | cut -d'.' -f1,2,3)
REVERSED_IP_PREFIX=$(echo $IP_PREFIX | awk -F '.' '{print $3"."$2"."$1}')
# 提取服务器IP的最后一段作为反向查找记录的最后一部分
LAST_OCTET=$(echo $DNS_IP | cut -d'.' -f4)
# 获取当前日期和时间(用于打印到日志中)
current_date_time=$(date '+%Y-%m-%d %H:%M:%S')
# 获取当前日期并生成序列号(用于加入到区域文件中)
CURRENT_DATE=$(date +%Y%m%d)
SERIAL_NUMBER="${CURRENT_DATE}01"
#################创建相关目录及文件############
# 创建域名解析记录示例文件(用于更新正向区域文件和反向区域文件)
# 创建数据目录,用于存放统计信息和缓存数据等信息
DATA_DIR="$INSTALL_DIR/data"
mkdir -p "$DATA_DIR" || { echo "创建数据目录失败!"; exit 1; }
# 创建日志目录
LOGS_DIR="$INSTALL_DIR/log"
mkdir -p "$LOGS_DIR" || { echo "创建日志目录失败!"; exit 1; }
# 创建安装日志文件
LOG_FILE="${LOGS_DIR}/install.log"
touch $LOG_FILE
chmod +x $LOG_FILE
# 创建服务运行日志文件
SERVICE_LOG_FILE="${LOGS_DIR}/named.log"
touch $SERVICE_LOG_FILE
chmod +x $SERVICE_LOG_FILE
# 创建区域文件更新日志文件(记录区域文件md5值及序列号的更新)
ZONE_LOG_FILE="${LOGS_DIR}/updateZone.log"
touch $ZONE_LOG_FILE
# 创建区域文件目录
ZONES_DIR="$INSTALL_DIR/zones"
mkdir -p "$ZONES_DIR" || { echo "创建 zones 目录失败!"; exit 1; }
# 创建带示例的解析记录文件
RECORDS_FILE="${INSTALL_DIR}/dns_records"
log_info "创建示例记录文件:$RECORDS_FILE"
cat > "$RECORDS_FILE" < # DNS 记录配置示例文件 # 文件格式:<主机名> # 注:以 # 开头的行为注释,不会被解析 # 示例记录(不指定记录类型,均视为A记录): # www 10.56.223.16 # 示例:表示主机名为 www,IP 地址为 10.56.223.16 # mail 10.56.223.17 # 示例:表示主机名为 mail,IP 地址为 10.56.223.17 # db 10.56.223.18 # 示例:表示主机名为 db,IP 地址为 10.56.223.18 # 请根据需要添加实际的 DNS 记录到此文件中。 # 保存文件后,运行更新脚本即可自动将记录写入正向和反向区域文件。 EOL # 创建解析记录文件当前md5值的文件 RECORD_MD5_FILE="${INSTALL_DIR}/dns_records_md5" log_info "创建解析记录文件当前md5值的文件:$RECORD_MD5_FILE,并写入$RECORDS_FILE 当前的md5值" md5sum "$RECORDS_FILE" | awk '{print $1}' > "$RECORD_MD5_FILE" # 设置防火墙规则 log_info "配置防火墙..." firewall-cmd --permanent --add-service=dns >> $LOG_FILE 2>&1 firewall-cmd --reload >> $LOG_FILE 2>&1 # 安装必要的包 log_info "安装BIND..." yum install -y bind bind-utils >> $LOG_FILE 2>&1 # 配置BIND服务 log_info "配置BIND..." # 创建BIND配置文件 cat > $INSTALL_DIR/named.conf < options { listen-on port 53 { any; }; listen-on-v6 { any; }; allow-query { any; }; allow-query-cache { any; }; // 允许查询缓存 recursion $RECURSION; // 是否允许允许递归查询 allow-recursion { any; }; directory "$INSTALL_DIR"; dump-file "$INSTALL_DIR/data/cache_dump.db"; // 转储文件 statistics-file "$INSTALL_DIR/data/named_stats.txt"; // 统计文件 memstatistics-file "$INSTALL_DIR/data/named_mem_stats.txt"; // 内存统计文件 recursing-file "$INSTALL_DIR/data/named.recursing"; // 递归查询失败记录文件 // 转发器配置 forwarders { EOL # 检查 named.conf 是否写入成功 if [ $? -ne 0 ]; then log_info "写入 named.conf 失败。" exit 1 fi # 配置转发器 log_info "配置转发器..." if [ "$FORWARDERS" != "NONE" ]; then FORWARDER_ARRAY=(${FORWARDERS//\// }) FORWARDER_CONFIG="" for FORWARDER in "${FORWARDER_ARRAY[@]}"; do FORWARDER_CONFIG="$FORWARDER_CONFIG $FORWARDER;\n" done echo -e "$FORWARDER_CONFIG" >> $INSTALL_DIR/named.conf fi # 配置正向查找区域 log_info "配置正向查找区域..." cat > $ZONES_DIR/$PRIMARY_DOMAIN.zone < \$TTL 86400 @ IN SOA $SUB_DOMAIN.$PRIMARY_DOMAIN. $ADMIN_EMAIL. ( $SERIAL_NUMBER ; Serial: 序列号,用于标记区域文件版本(当更新区域文件时,需增加序列号,便于从服务器检测变化) 3600 ; Refresh: 从服务器刷新间隔(从服务器多久检查一次主服务器更新)(秒) 1800 ; Retry: 从服务器重试时间间隔(从服务器在刷新失败时的重试间隔)(秒) 1209600 ; Expire: 从服务器过期时间(从服务器在无法连接主服务器时,缓存数据失效的时间。)(秒) 86400 ; Minimum TTL: 最短存活时间(DNS缓存时间)(秒) ) ;NS 记录 @ IN NS $SUB_DOMAIN.$PRIMARY_DOMAIN. ;DNS服务器自身的A记录(第一条A记录表示可以通过主域名$PRIMAY_DOMAIN直接解析到域名服务器$DNS_IP) @ IN A $DNS_IP $SUB_DOMAIN IN A $DNS_IP ;在此处添加记录 EOL # 检查正向查找区域文件是否写入成功 if [ $? -ne 0 ]; then log_info "写入正向查找区域文件失败。" exit 1 else log_info "写入正向查找区域文件成功。" fi # 配置反向查找区域 log_info "配置反向查找区域..." cat > $ZONES_DIR/$REVERSED_IP_PREFIX.in-addr.arpa < \$TTL 86400 @ IN SOA $SUB_DOMAIN.$PRIMARY_DOMAIN. $ADMIN_EMAIL. ( $SERIAL_NUMBER ; Serial: 序列号,用于标记区域文件版本(当更新区域文件时,需增加序列号,便于从服务器检测变化) 3600 ; Refresh: 从服务器刷新间隔(从服务器多久检查一次主服务器更新)(秒) 1800 ; Retry: 从服务器重试时间间隔(从服务器在刷新失败时的重试间隔)(秒) 1209600 ; Expire: 从服务器过期时间(从服务器在无法连接主服务器时,缓存数据失效的时间。)(秒) 86400 ; Minimum TTL: 最短存活时间(DNS缓存时间)(秒) ) ;NS 记录 @ IN NS $SUB_DOMAIN.$PRIMARY_DOMAIN. ;DNS服务器自身的PTR记录 $LAST_OCTET IN PTR $SUB_DOMAIN.$PRIMARY_DOMAIN. ;在此处添加记录 EOL # 检查反向查找区域文件是否写入成功 if [ $? -ne 0 ]; then log_info "写入反向查找区域文件失败。" exit 1 else log_info "写入反向查找区域文件成功。" fi # 添加正向查找区域和反向查找区域到配置文件named.conf中,并增加日志模块 cat >> $INSTALL_DIR/named.conf < }; }; zone "$PRIMARY_DOMAIN" IN { //默认将DNS服务器设置为主域名服务器 type master; //标明为主域名服务器 file "$INSTALL_DIR/zones/$PRIMARY_DOMAIN.zone"; // 正向查找区域文件 allow-transfer { any; }; // 允许所有从域名服务器进行区域传输 allow-update { none; }; // 不允许任何服务器更新区域文件(该参数仅适用于主域名服务器,如果设置IP,则允许修改[通过通过DNS协议进行修改]) //若需将DNS服务器改为从域名服务器,换成如下4个参数 #type slave; //标明为从域名服务器 #file "$INSTALL_DIR/zones/$PRIMARY_DOMAIN.zone"; // 正向查找区域文件 #masters { $DNS_IP; }; // 需要进行区域传输的主域名服务器(该参数仅适用于从域名服务器) #masterfile-format text; // 指定存储为文本格式(该参数仅适用于从域名服务器,若不设置该参数,从域名服务器同步后的区域文件会显示乱码) }; zone "$REVERSED_IP_PREFIX.in-addr.arpa" IN { //默认将DNS服务器设置为主域名服务器 type master; //标明为主域名服务器 file "$INSTALL_DIR/zones/$REVERSED_IP_PREFIX.in-addr.arpa"; // 反向查找区域文件 allow-transfer { any; }; // 允许所有从域名服务器进行区域传输 allow-update { none; }; // 不允许任何服务器更新区域文件(该参数仅适用于主域名服务器,如果设置IP,则允许修改[通过通过DNS协议进行修改]) //若需将DNS服务器改为从域名服务器,换成如下4个参数 #type slave; //标明为从域名服务器 #file "$INSTALL_DIR/zones/$REVERSED_IP_PREFIX.in-addr.arpa"; // 反向查找区域文件 #masters { $DNS_IP; }; // 需要进行区域传输的主域名服务器(该参数仅适用于从域名服务器) #masterfile-format text; // 指定存储为文本格式(该参数仅适用于从域名服务器,若不设置该参数,从域名服务器同步后的区域文件会显示乱码) }; logging { channel default_file { file "$SERVICE_LOG_FILE" versions 3 size 5m; severity info; print-time yes; print-severity yes; print-category yes; }; category default { default_file; }; }; EOL # 设置安装目录文件权限 log_info "设置文件权限..." chown named:named -R $INSTALL_DIR && chmod -R 750 $INSTALL_DIR || { log_err "设置安装目录文件权限失败。" exit 1 } # 配置 BIND 服务使用自定义路径 log_info "配置 BIND 服务使用自定义路径的配置文件..." sed -i "s@/etc/named.conf@$INSTALL_DIR/named.conf@g" /usr/lib/systemd/system/named.service || { log_err "修改 named.service 配置失败。" exit 1 } systemctl daemon-reload # 配置SELinux log_info "配置SELinux..." setsebool -P named_tcp_bind_http_port 1 # 启动并启用 BIND 服务 log_info "启动并启用 BIND 服务..." systemctl start named && systemctl enable named || { log_err "BIND 服务启动失败,请检查配置。" exit 1 } # 生成转储文件和统计文件 log_info "生成转储文件..." rndc dumpdb || { log_err "生成转储文件失败!"; exit 1; } log_info "生成统计文件..." rndc stats || { log_err "生成统计文件失败!"; exit 1; } # 添加定时任务(更新记录频率为0时不添加) if [[ "$UPDATE_TIME" -eq 0 ]]; then log_warn "不添加更新定时更新任务。" else ( echo "*/$UPDATE_TIME * * * * cd $INSTALL_DIR && ./update_zone.sh $PRIMARY_DOMAIN.zone $REVERSED_IP_PREFIX.in-addr.arpa" ) | crontab - log_info "添加定时任务(更新区域文件)成功,每 $UPDATE_TIME 分钟更新。" fi # 检查服务状态 if systemctl status named &>/dev/null; then log_info "DNS 服务配置成功!" log_info "DNS 服务器地址为:$DNS_IP" else log_err "DNS 服务启动异常,请检查日志。" exit 1 fi 使用示例: 2.2 解析 正向解析 nslookup dnsServer15.testDNS.org 10.56.223.15 反向解析 nslookup 10.56.223.15 10.56.223.15 也可通过dig命令查看详细的解析信息(包括从哪个区域文件查询的结果等),如下所示: dig命令还能查看通过递归查询的详细解析路径: dig @10.56.223.15 www.baidu.com +trace 2.3 卸载 #!/bin/bash # 日志文件(/dev/null表示不记录日志,当然也可以不定义该变量,同时把几个写入日志方法里的">> $LOG_FILE"去掉,这里是为了适配写入日志的方法) LOG_FILE="/dev/null" # 打印提示信息并写入日志 function log_info(){ content="[INFO] $(date '+%Y-%m-%d %H:%M:%S') $@" echo $content >> $LOG_FILE && echo -e "\033[32m" ${content} "\033[0m" } # 打印警告信息并写入日志 function log_warn(){ content="[WARN] $(date '+%Y-%m-%d %H:%M:%S') $@" echo $content >> $LOG_FILE && echo -e "\033[33m" ${content} "\033[0m" } # 打印错误信息并写入日志 function log_err(){ content="[ERROR] $(date '+%Y-%m-%d %H:%M:%S') $@" echo $content >> $LOG_FILE && echo -e "\033[31m" ${content} "\033[0m" } # 使用说明 usage() { echo "脚本用途:" echo -e "\033[32m卸载DNS服务\033[0m" echo -e "\n参数:" echo -e "\033[32m共1个参数:安装目录\033[0m" echo -e "\n示例:" echo -e "\033[32m$0 /opt/dns\033[0m" exit 1 } # 如果传入参数数量不正确,则显示使用说明 if [ $# -ne 1 ]; then usage fi # 解析传入参数 INSTALL_DIR=$1 # 打印解析后的参数 echo -e "\033[32m"----------------------------"\033[0m" echo "将卸载的安装目录:$INSTALL_DIR" echo -e "\033[32m"----------------------------"\033[0m" # 检查目录是否存在 if [ ! -d "$INSTALL_DIR" ]; then log_warn "安装目录$INSTALL_DIR不存在!" exit 1 fi # 停止BIND服务 log_info "停止BIND服务..." systemctl stop named # 禁用BIND服务开机启动 log_info "禁用BIND开机启动..." systemctl disable named # 删除安装目录 log_warn "删除安装目录 $INSTALL_DIR ..." find "$INSTALL_DIR" -depth -type d -exec rm -rf {} + # 删除防火墙规则 log_info "删除防火墙规则..." firewall-cmd --permanent --remove-service=dns firewall-cmd --reload # 删除定时任务 log_info "删除定时任务..." crontab -l | grep -v 'update_zone.sh' | crontab - # 删除SELinux配置 log_info "删除SELinux配置..." setsebool -P named_tcp_bind_http_port 0 # 完成卸载 log_info "卸载完成!" 使用示例: 2.4 更新 这里的更新是指更新区域文件,通常在更新主域名服务器的域名解析记录后,需要手动或通过脚本自动(一般使用DNS协议)更新区域文件中的记录以及序列号,以便从域名服务器能够同步。 本文以安装目录下的文件 dns_records 作为域名解析记录文件,先通过脚本 update_zone.sh 判断文件 dns_records md5值是否发生变化,从而判断是否需要更新区域文件中的记录以及序列号,并将update_zone.sh加入定时任务,从而实现:当修改域名解析记录文件时,定时自动更新区域文件中的记录及其序列号。 #!/bin/bash # 通过 cron 触发时,cron 的环境变量较少,通常 PATH 是非常简化的,只包括 /usr/bin 和 /bin,而脚本中使用的named-checkzone命令在/usr/sbin,因此需要添加path变量 export PATH=$PATH:/usr/sbin:/sbin # 获取当前目录(即安装目录) INSTALL_DIR=$(pwd) # 确定 解析记录文件、记录文件的md5值文件、区域文件所在目录、区域文件更新日志文件 RECORDS_FILE=$INSTALL_DIR/dns_records RECORD_MD5_FILE="${INSTALL_DIR}/dns_records_md5" ZONE_DIR="$INSTALL_DIR/zones" LOG_FILE="$INSTALL_DIR/log/updateZone.log" # 打印提示信息并写入日志 function log_info(){ content="[INFO] $(date '+%Y-%m-%d %H:%M:%S') $@" echo $content >> $LOG_FILE && echo -e "\033[32m" ${content} "\033[0m" } # 打印警告信息并写入日志 function log_warn(){ content="[WARN] $(date '+%Y-%m-%d %H:%M:%S') $@" echo $content >> $LOG_FILE && echo -e "\033[33m" ${content} "\033[0m" } # 打印错误信息并写入日志 function log_err(){ content="[ERROR] $(date '+%Y-%m-%d %H:%M:%S') $@" echo $content >> $LOG_FILE && echo -e "\033[31m" ${content} "\033[0m" } # 使用说明 usage() { echo "脚本用途:" echo -e "\033[32m当检测到解析记录文件md5值发生变化时,自动更新区域文件的记录和序列号\033[0m" echo -e "\n参数:" echo -e "\033[32m共2个,分别为:正向区域文件、反向区域文件\033[0m" echo -e "\n示例:" echo -e "\033[32m$0 testDNS.org.zone 223.56.10.in-addr.arpa\033[0m" exit 1 } # 如果传入参数数量不正确,则显示使用说明 if [ $# -ne 2 ]; then usage fi # 解析传入参数 FORWARD_FILE=${INSTALL_DIR}/zones/$1 REVERSE_FILE=${INSTALL_DIR}/zones/$2 # 打印解析后的参数 echo -e "\033[32m"----------------------------"\033[0m" echo "正向区域文件:$FORWARD_FILE" echo "反向区域文件:$REVERSE_FILE" echo -e "\033[32m"----------------------------"\033[0m" # 根据传入的区域文件名获取域名和IP地址前缀 dName=$1 domainName=${dName%zone} prefix=$2 ip_prefix=${prefix%.in-addr.arpa} ######################################################################## # 更新区域文件解析记录的方法 function update_zone_record(){ # 检查区域文件是否存在 if [ ! -f "$FORWARD_FILE" ]; then log_err "正向区域文件 $FORWARD_FILE 不存在!" exit 1 fi if [ ! -f "$REVERSE_FILE" ]; then log_err "反向区域文件 $REVERSE_FILE 不存在!" exit 1 fi log_warn "更新区域文件 $FORWARD_FILE 和 $REVERSE_FILE 的解析记录" # 更新正向区域文件前先清空原有A记录 sed -i '/在此处添加记录/,${ /^;在此处添加记录$/!d }' "$FORWARD_FILE" # 更新反向区域文件前先清空原有PTR记录 sed -i '/在此处添加记录/,${ /^;在此处添加记录$/!d }' "$REVERSE_FILE" # 使用while循环读取文件 while IFS=$' \t' read -r RECORD_NAME IP_ADDRESS || [[ -n "$RECORD_NAME" ]]; do # 跳过空行或注释行 if [[ -z "$RECORD_NAME" || -z "$IP_ADDRESS" || "$RECORD_NAME" =~ ^# ]]; then continue fi # 提取反向查找信息 LAST_OCTET=$(echo $IP_ADDRESS | awk -F '.' '{print $4}') REVERSED_IP_PREFIX=$(echo $IP_ADDRESS | awk -F '.' '{print $3"."$2"."$1}') # 在正向区域文件中添加A记录 log_info "添加A记录:$RECORD_NAME IN A $IP_ADDRESS 到正向区域文件 $FORWARD_FILE" echo "$RECORD_NAME IN A $IP_ADDRESS" >> "$FORWARD_FILE" || { log_err "更新正向区域文件失败!" exit 1 } # 在反向区域文件中添加PTR记录 log_info "添加PTR记录:$LAST_OCTET IN PTR $RECORD_NAME.$domainName 到反向区域文件 $REVERSE_FILE" echo "$LAST_OCTET IN PTR $RECORD_NAME.$domainName" >> "$REVERSE_FILE" || { log_err "更新反向区域文件失败!" exit 1 } done < "$RECORDS_FILE" log_info "区域文件 $FORWARD_FILE 和 $REVERSE_FILE 解析记录更新成功!" } # 更新区域文件序列号的方法 function update_zone_serial(){ log_warn "更新区域文件 $FORWARD_FILE 和 $REVERSE_FILE 的序列号" zone_file_list=("$FORWARD_FILE" "$REVERSE_FILE") # 遍历文件列表 for file in "${zone_file_list[@]}"; do # 获取当前序列号 original_serial=$(awk '/^[^;]*[0-9]{10}/ {print substr($0, match($0, /[0-9]{10}/), 10)}' "$file") # 判断是否成功获取序列号 if [[ -z "$original_serial" ]]; then log_err "无法获取 $file 的序列号" continue fi # 将序列号加 1 new_serial=$((original_serial + 1)) # 更新文件中的序列号 sed -i "s/$original_serial/$new_serial/" "$file" # 获取更新后的序列号 updated_serial=$(awk '/^[^;]*[0-9]{10}/ {print substr($0, match($0, /[0-9]{10}/), 10)}' "$file") # 确认序列号是否增加了1 if [[ $((updated_serial - original_serial)) -eq 1 ]]; then log_info "区域文件 $file 序列号更新成功,更新前: $original_serial,更新后: $updated_serial" else log_err "区域文件 $file 序列号更新失败" fi done } ######################################################################## # 获取记录文件之前的 MD5 值 previous_md5=$(cat "$RECORD_MD5_FILE") # 获取当前文件的 MD5 值 current_md5=$(md5sum "$RECORDS_FILE" | awk '{print $1}') # 判断 MD5 值是否变化 if [[ "$current_md5" != "$previous_md5" ]]; then log_warn "解析记录文件$RECORDS_FILE的md5值发生变化,即将更新区域文件..." # 更新记录文件的md5值 md5sum "$RECORDS_FILE" | awk '{print $1}' > "$RECORD_MD5_FILE" # 先更新区域文件记录 update_zone_record # 再更新区域文件序列号 update_zone_serial # 验证更新后的区域文件 echo "验证正向区域文件 $FORWARD_FILE ..." named-checkzone "$domainName" "$FORWARD_FILE" || { log_err "正向区域文件 $FORWARD_FILE 验证失败!" exit 1 } echo "验证反向区域文件 $REVERSE_FILE ..." named-checkzone "$ip_prefix" "$REVERSE_FILE" || { log_err "反向区域文件 $REVERSE_FILE 验证失败!" exit 1 } log_info "区域文件 $FORWARD_FILE 和 $REVERSE_FILE 验证成功!" # 重新加载 BIND 配置 echo "重新加载 BIND 配置..." rndc reload || { log_err "BIND 配置重载失败!" exit 1 } else log_info "$RECORDS_FILE的md5值未发生变化,区域文件无需更新" exit 1 fi 安装完成后会生成类似如下定时任务: 若添加新的区域文件,可参考添加新的定时任务(只需要修改update_zone.sh参数为新的区域文件即可)。 当定时任务触发时,若检测到dns_records文件的md5值未发生变化,表明其解析记录也未发生变化,因此不会更新区域文件,与如下手动执行脚本结果类似: 当定时任务触发时,若检测到dns_records文件的md5值发生变化,表明其解析记录也已发生变化,因此会更新区域文件,与如下手动执行脚本结果类似: 2.5 同步 这里的同步是指主域名服务器上的区域文件向从域名服务器进行同步,区域文件的同步通过区域传输方式来实现,主服务器仅向允许的从服务器同步,从服务器仅接受允许的主服务器的同步(均在named.conf配置文件中进行设置,详见前面的安装脚本),同步的频率以主服务器上区域文件中设置的刷新频率(默认为1小时,实际更新可能会有几分钟的误差)为准。 主服务器可设置1台、多台或任何从服务器进行同步 不论主服务器上区域文件序列号是否发生变化,从服务器上区域文件的时间都会按主服务器上设置的刷新频率进行更新 从服务器也可设置从1台或多台主服务器进行同步,当设置多台时,会逐一尝试,直到同步成功 从服务器无需区域文件(当主服务器同步时会自动创建),若已存在,当主服务器同步时会覆盖 从服务器重启named服务时,会自动触发同步,而不需要等到刷新时间到达才同步 配置文件named.conf修改后,可通过named-checkconf named.conf命令验证是否合法,然后执行rndc reload命令即时生效而不用重启named服务 从服务器完成同步后,正向区域文件类似如下: 反向区域文件类似如下: 3 DNS解析优先级 DNS解析优先级从高到低依次如下: hosts 文件(静态优先) 本地缓存(快速解析) VPN 配置的 DNS(动态优先) 本地网络配置的 DNS(DHCP 或手动) ISP 提供的 DNS或默认根服务器 当本地网络配置自动获取DNS时,将使用出口网关配置的DNS(对于家庭网络,即通过运营商提供的网关上配置的DNS,例如:移动的光猫上配置的DNS)进行解析。 4 总结 本文分别介绍了在Windows和Linux下本地搭建DNS的方法,对于Linux下的搭建还附上了安装脚本。