几种搭建内网穿透环境的方案及实现(二):bind9搭建私有dns服务器并实现ddns

» intranet penetration

通过前面章节的介绍,我们了解了内网穿透的各种方案,本文将着重于利用bind9搭建的方式来实现内网穿透。整个搭建方案分为三部分:1、配置dnspod解析记录;2、利用bind9搭建私有dns服务器;3、ddns实现脚本;

配置dns供应商的解析记录(以dnspod为例)

1、添加A记录,将域名dns.domain.com解析至服务器的ip地址,即将该域名指向dns服务器:

2、添加NS记录,将webdav.domain.com的解析服务器设定为1中设置的dns服务器

利用上面的两步即可将域名webdav.domain.com交由自己的dns服务器进行解析。

对解析记录不了解的可以参考: 域名解析的记录类型:A记录、CNAME…

利用bind9搭建私有dns服务器(以ubuntu系统为例)

输入以下命令安装bind9: sudo apt install bind9 bind9的默认路径如下:

配置文件
/etc/resolv.conf  # 配置解析服务器的地址
/etc/bind/named.conf  # 主配置文件,自动include下面两个配置文件中的信息(.local/.option)
/etc/bind/named.conf.local  # 列出了所有区域数据文件(.zone)的信息
/etc/bind/named.conf.option  # 定义选项
区域数据文件
/var/cache/bind/  # 工作路径,用于存放.zone的区域配置文件
/var/cache/bind/kasumiksm.com.zone  # 定义区域域名解析的具体参数
# 文件名命名格式无限定
/var/cache/bind/ip.zone  # 定义反向解析时的具体参数
# 文件名命名格式无限定

/etc/resolv.conf

nameserver 127.0.0.1  # 设置本地采用的域名解析服务器地址。
# 欲利用nslookup查询bind9设置情况时需要将其设置为127.0.0.1
# 但是当设置为127.0.0.1地址后,本所有dns流量均被其劫持可能会导致无法上网,
# 正确的方式是添加在设置为127.0.0.1后,在添加如下的一条规则
nameserver 8.8.8.8
# 但是在该文件中配置的信息,在重启systemd-resolved服务后均将失效
永久修改的方法如下
# 在/etc/network/interfaces文件中添加配置即可
dns-nameservers 8.8.8.8
dns-nameservers 127.0.0.1

关于防止/etc/resolv.conf被失效的方法详情见: ubuntu解决resolv.conf被重写问题

/etc/bind/named.conf

一般无需配置,默认即可

// This is the primary configuration file for the BIND DNS server named.
//
// Please read /usr/share/doc/bind9/README.Debian.gz for information on the
// structure of BIND configuration files in Debian, *BEFORE* you customize
// this configuration file.
//
// If you are just adding zones, please do that in /etc/bind/named.conf.local

include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.local";
include "/etc/bind/named.conf.default-zones";

/etc/bind/named.conf.local

//
// Do any local configuration here
//

// Consider adding the 1918 zones here, if they are not used in your
// organization
//include "/etc/bind/zones.rfc1918";

//key "ddns" {
//        algorithm hmac-md5;
//        secret "GAgsKfYTueMc4lawB8qKzg==";
//};

zone "kasumiksm.com" IN {
        type master;
        file "/var/cache/bind/kasumiksm.com.zone";
};
// 添加一个正向解析的区域,当需要查询的域名的根域名为kasumiksm.com时
// 均会查询该区域。即不可将携带其他根域名的域名放在该区域。
// 如需让该区域负责所有.com结尾的域名时,将"kasumiksm.com"改为
// "com"即可(未测试)
zone "117.1.in-addr.arpa" IN {
        type master;
        file "/var/cache/bind/ip.zone";
};
// 添加一个反向解析的区域,并非必须的区域,当需要查询的ip的头几位为1.117时
// 将进入该区域查询,勿将其他开头的ip地址放入该区域(注意这里的ip需反向写)
// 如需将该区域设置为反向解析所有的ip,则将"117.1.in-addr.arpa"改为
// "in-addr.arpa"即可(已测试)

/etc/bind/named.conf.option

options {
        directory "/var/cache/bind";
		//设置工作路径

        // If there is a firewall between you and nameservers you want
        // to talk to, you may need to fix the firewall to allow multiple
        // ports to talk.  See http://www.kb.cert.org/vuls/id/800113

        // If your ISP provided one or more IP addresses for stable
        // nameservers, you probably want to use them as forwarders.
        // Uncomment the following block, and insert the addresses replacing
        // the all-0's placeholder.

        // forwarders {
        //      0.0.0.0;
        // };

        //========================================================================
        // If BIND logs error messages about the root key being expired,
        // you will need to update your keys.  See https://www.isc.org/bind-keys
        //========================================================================
        dnssec-validation auto;

        listen-on-v6 { any; };
	//开启listen-on-v6就会默认也监听ipv4。
	//listen-on { 5.6.7.8; };会默认监听5.6.7.8:53,即在5.6.7.8监听53端口
	//listen-on port 1234 { 127.0.0.1; };会监听127.0.0.1:1234
        allow-query {any;};
	//定义可以进行dns查询的主机,如:allow-query {127.0.0.1;};只能通过本机查询
	//allow-query也能在zone语句中设定,这样全局options中的allow-query选项
	//在这里就不起作用了。
        auth-nxdomain no;
	//默认即可
};

更进一步的设置参考: DNS域名解析服务器 BIND配置文件详解(二)

/var/cache/bind/kasumiksm.com.zone

$TTL	10 //ttl 10秒	//dns服务器的域名	//管理员的邮箱,为了防止歧义将@改为.
@       IN      SOA     dns.kasumiksm.com.      1412251241.qq.com.(
                                10 ;serial //序列号
                                1M ;refresh
                                1M ;retry
                                1M ;expire
                                1M) ;negative cache ttl
//域名	interntet 记录类型 正向解析的结果	
@       IN      NS      dns.kasumiksm.com.
dns     IN      A       1.117.247.180
webdav  IN      A       2.117.2.2
abc     IN      A       2.117.2.2

当域名写成dnsdns.分别代表dns.kasumiksm.com.以及dns. 即如果没有点则会自动在后面补充区域的域名

/var/cache/bind/ip.zone

$TTL    10
@       IN      SOA     dns.kasumiksm.com.      1412251241.qq.com.(
                                10      ;serial
                                1M  ;refresh
                                1M   ;retry
                                1M ;expire
                                1M) ;negative cache ttl
@       IN      NS      dns.kasumiksm.com.
180.247.117.1   IN      PTR     dns.kasumiksm.com.
2.2.117.2       IN      PTR     webdav.kasumiksm.com.
2.2.117.2       IN      PTR     abc.kasumiksm.com.

与域名的设置类似,但是要注意将ip反向书写

检查写好的配置文件格式是否合规

利用

named-checkconf

named-checkzone

分别对配置文件及区域文件进行格式的检查。

参考链接:

基于Linux(CentOS/Ubuntu)使用Bind9自建私有权威DNS

bugshooting

因为之前将.zone文件直接放在/etc/bind/目录下,导致在利用nsupdate修改时出错(就算如何修改文件的权限,也会提示无权限创建.jnl文件的错误)遇到这个问题是因为ubuntu的Apparmor机制。可以在/var/log/kern.log看到错误日志。

想要去除该bug的方法有二: 方法一、运行指令sudo aa-complain /path/to/bin。其中/path/to/bin代表可执行文件的路径。该信息会在/var/log/kern.log中体现。 该指令可开启complain模式,使得Apparmor不再直接拦截应用的行为,但是仍会在/var/log/kern.log产生complain记录。 方法二、在/etc/apparmor.d修改相关程序的配置文件如对于bind9,其相关的配置文件为:/etc/apparmor.d/usr.sbin.named。通过指令 dpkg -S /etc/apparmor.d/usr.sbin.named 可以查询该配置文件对应的程序名称。例如:bind9

相关参考链接: DebuggingApparmor

参考链接:

基于Linux(CentOS/Ubuntu)使用Bind9自建私有权威DNS

DNS域名解析服务器

BIND配置文件详解(二)

ISC BIND9 - 最详细、最认真的从零开始的 BIND 9 - DNS服务搭建及其原理讲解

Bind 域名解析服务

ddns实现脚本

ddns脚本预备知识

rndc工具

运行命令sudo rndc reload即可重新加载bind9的所有配置文件及区域数据文件(.zone),进行配置及ip地址的更新。sudo rndc reload kasumiksm.com.zone可以加载单个区域文件进行更新。当运行指令后出现rndc: 'reload' failed: dynamic zone的提示。则需要修改named.conf.local文件。参见:利用rndc对单个zone进行reload的时候提示rndc: ‘reload’ failed: dynamic zone

参考链接: bind详解,主从DNS

利用rndc对单个zone进行reload的时候提示rndc: ‘reload’ failed: dynamic zone

ddns功能实现方式

利用python脚本结合bash,实现登录ssh主机修改.zone文件

本文采用的即是该方法,需要考虑以下问题: 1、利用python3中的paramiko模块可以通过ssh登录服务器(支持密码登录以及密钥登陆)。使用方法: python通过ssh连接服务器,执行命令

python ssh 连接远程服务器,修改文本内容,调用脚本

paramiko使用秘钥连接

2、对服务器的ubuntu系统进行设置,生成密钥,并使其支持密钥登陆。参考链接: UBUNTU设置SSH通过密钥登陆

ubuntu ssh密钥_生成SSH密钥以在Ubuntu中进行无密码登录

3、为避免执行sudo后,会出现的密码验证。需要对服务器进行如下设置: ubuntu sudo不需要输入密码的方法

4、为了实现ddns,必须先获得内网主机的公网ip地址。实现代码如下: python 获取公网 外网 ip 几种方式

5、通过bash语言中的sed指令对服务器上的.zone文件进行修改。 需要理解的指令有:

sed -i '$ahello' 1.txt # 在最后一行添加hello

sed -i '/123/d' 1.txt # 删除以123开头的行

更多参考链接:

如何使用bash / sed脚本删除文本文件的第一行?

Linux shell 用sed删除第一行、最后一行或增加删除某行

sed命令(删除与追加)

shell脚本–sed的用法

利用nsupdate工具

该方法本身存在一些问题,并不推荐。这里只做参考: bind9 实现动态解析域名 使用BIND配置动态DDNS

在github上有利用该方法实现ddns的例子,供参考: andreasscherbaum/bind-dynamic-dns-update

实现脚本

import paramiko
from urllib.request import urlopen
from json import load


class LinuxOrder:

    def __init__(self, demain, port, username, private_key_path, timeout):
        """
        :param demain: 服务器域名或地址均可
        :param port: ssh 连接的端口
        :param username: 服务器用户名
        :param private_key_path: 本地密钥路径
        :param timeout: 连接超时时间
        """
        try:
            self.demain = demain
            self.port = port
            self.username = username
            self.private_key = paramiko.RSAKey.from_private_key_file(private_key_path)
            self.timeout = timeout
            self.ssh = paramiko.SSHClient()
            self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            self.ssh.connect(self.demain, self.port, self.username, pkey=self.private_key, timeout=self.timeout)
            print('{0}:连接成功'.format(self.demain))
        except Exception as e:
            print('{0}:连接失败'.format(self.demain))
            raise e

    def run(self, order):
        """
        执行代码
        :param order: 命令
        :return:
        """
        stdin, stdout, stderr = self.ssh.exec_command(order)
        print(stderr.read())
        print(stdout.read())

    def close_ssh(self):
        """关闭ssh连接"""
        self.ssh.close()
        print('关闭ssh连接')


if __name__ == '__main__':
    demain = 'dns.kasumiksm.com'  # dns服务器域名或ip地址均可
    port = '22'
    usr = 'ubuntu'
    timeout = 3
    private_key_path = 'cvm_shanghai3.pem'  # 本地密匙
    ddns_ip = load(urlopen('https://api.ipify.org/?format=json'))['ip']  # 获取ddns的公网ip

    # 第一行为删除以webdav开始的行,第二行为添加webdav的解析记录,第三行为更改区域文件的权限,第四行为重载区域文件及配置文件,更新ip
    order = "sudo sed -i '/^webdav/d' /var/cache/bind/kasumiksm.com.zone\n" \
            "sudo sed -i '$awebdav\tIN\tA\t" + ddns_ip + "' /var/cache/bind/kasumiksm.com.zone\n" \
            "sudo chown bind:bind /var/cache/bind/kasumiksm.com.zone\n" \
            "sudo rndc reload\n"

    L = LinuxOrder(demain, port, usr, private_key_path, timeout)
    L.run(order)
    L.close_ssh()

dns测试工具

正向解析: nslookup webdav.kasumiksm.com 得出如下结果即为设置成功

Server:		127.0.0.1
Address:	127.0.0.1#53

Name:	webdav.kasumiksm.com
Address: 2.2.2.2

反向解析: nslookup 1.117.247.180 得出如下结果即为设置成功

180.247.117.1.in-addr.arpa	name = dns.kasumiksm.com.