网上关于DDNS解析的介绍已经很多了,我这里主要列举通过阿里云和Cloudflare进行解析。使用两个解析的原因是阿里云的域名必须要备案,不然会被阿里云封掉(阿里云解析的域名如果解析的IP不是阿里云服务器的,还会提示要求使用阿里云服务器。)。而Cloudflare不用备案,可以直接用。在阿里云申请的域名可以在阿里云上配置为通过Cloudflare解析。当然不只Cloudflare,其实有很多提供DNS解析服务的厂商都提供了DNS的API,就不一一列举了。

群晖使用Docker cli 群晖使用阿里云ddns_群晖


如果买的群晖服务器的话,本身群晖会送一个域名,也会自动做DDNS解析,不需要自己折腾。如果没有群晖的服务器或者想用自己的已经申请的域名,就需要自己做DDNS解析。DDNS解析说白了就是调用第三方提供的更新DNS的API。

步骤如下:

  1. 获取当前的公网IP;
  2. 对比当前公网IP是否变化;
  3. 若发生变化则更新DNS解析。

下面是具体实现过程,我通过python来实现。

1.获取当前的公网IP

获取公网ip目前我有四种方式:

  1. 通过www.ifconfig.me获取,因为群晖本身也是linux环境,代码跑在群晖上,可以直接curl www.ifconfig.me -s得到公网ip。不过这个网站好像是国外的,有时候访问很慢,可能会获取超时,这时候可以尝试其他方式。
  2. 通过200019.ip138.com获取,这是国内的获取ip,百度上搜ip就是调用的这个网站的。优点速度快,缺点要自己分析页面提取ip。
  3. 自己搭一个nginx服务器,然后配置nginx获取真实IP。优点不会受制于别人,获取方便。缺点是有个服务器,要自己部署个程序。
  4. 自己写代码解析获取真实IP,这个我没试过,理论上也是可以的,不然nginx怎么能拿到的?

nginx获取公网ip,配置如下:

server {
       listen       80;
       server_name  localhost;

       location / {
           default_type text/html;
           return 200 "$remote_addr";
       }
   }

可以测试下: curl http://localhost

下面的代码中实现了前两种方式,其实我也实现了第三种方式,但我自己的域名安全起见,不方便公开,代码就不公布了,其实也简单,就是一个request发送get请求就好了。

# -*- coding:UTF-8 -*-
import re
import subprocess

import requests

from util.tonggu_logger import TongguLogger

logger = TongguLogger().getLogger()

"""
    get network ip
    @version: python3.x
    @author: wangjf
    @software: PyCharm
    @file: collect.py
    @time: 2019/7/8 19:30
"""
def get_network_ip():
    ip = getIpFromIfConfig()
    if ip is None:
        ip = getIpFromIp138()
    if len(ip) == 0:
        return None
    # extract ip
    result = re.findall(
        r"\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b", ip)
    if result:
        return result[0]
    return None

"""
    get ip from www.ifconfig.me
    @version: python3.x
    @author: wangjf
    @software: PyCharm
    @file: collect.py
    @time: 2019/7/8 19:30
"""
def getIpFromIfConfig():
    try:
        logger.info("start get ip from www.ifconfig.me")
        rows = subprocess.getoutput('curl www.ifconfig.me -s')
        if rows:
            logger.info("get ip %s" % rows)
            ip = rows
        else:
            raise Exception('get ip from www.ifconfig.me failed')
        return ip
    except Exception:
        logger.error("get ip from www.ifconfig.me failed", exc_info=True)
    return None

"""
    get ip from ip138.com
    @version: python3.x
    @author: wangjf
    @software: PyCharm
    @file: collect.py
    @time: 2019/7/8 19:35
"""
def getIpFromIp138():
    try:
        logger.info("start get ip from 200019.ip138.com")
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
        }
        html = requests.get('http://200019.ip138.com/', headers=headers)
        rows = re.findall('<p align="center">(.*?)</p>', html.text, re.S)
        if rows:
            logger.info("get ip %s" % rows)
            ip = rows[0]
        else:
            raise Exception('get ip from 200019.ip138.com failed')
        return ip
    except Exception:
        logger.error("get ip from 200019.ip138.com failed", exc_info=True)
    return None
2.对比当前公网IP是否变化

这个步骤其实比较简单,把获取的对公ip存在本地文件,然后下次获取对公ip的时候判断新获取的ip与文件中的ip是否一致,不一致则修改。
下面的代码中包含了获取对公ip、判断ip是否变化以及更新dns。若用的阿里云,则修改ali这段,若用的cloudflare则修改flare这段。

# -*- coding:UTF-8 -*-
from ddns.collect import get_network_ip
from ddns.alicloud import AliCloudDns
from ddns.cloudflare import CloudflareDns

import sys
from util.tonggu_logger import TongguLogger
import util.path
import pathlib

logger = TongguLogger().getLogger()

ali = {
    "access_key_id": "xxx",
    "access_secret": "xxx",
    "region_id": "cn-hangzhou",
    "domain": "xxx.com",
    "domain-a": "home"
}

flare = {
    "access_key_id": "xxx@163.com",
    "access_secret": "xxx",
    "region_id": "xxx",
    "domain": "xxx.com",
    "domain-a": "home"
}

# save content and return true if changed, otherwise return false
def save_content(path, filename, content):
    path = pathlib.Path(str)
    if not path.exists():
        path.mkdir()
    file = pathlib.Path(path + '/' + filename)
    if file.exists():
        with file.open() as f:
           str = f.readline()
           if content == str:
               return False

    # write file
    with file.open('w') as f:
        f.writelines(content)
    return True

if __name__ == "__main__":
    try:
        network_ip = get_network_ip()
        if network_ip is None:
            raise Exception("get ip failed")

        # check ip is changed
        if save_content('/tmp', 'ip', network_ip) == False:
            sys.exit(2)
    except Exception:
        logger.error("get ip failed", exc_info=True)
        sys.exit(2)

    # update dns
    try:
        # update alicloud
        logger.info("updata aliyun dns")
        aliCloudDns = AliCloudDns(ali["access_key_id"], ali["access_secret"], ali["region_id"])
        record = aliCloudDns.DescribeDomainRecords(ali["domain"], ali["domain-a"])
        aliCloudDns.UpdateDomainRecord(record, network_ip)
        logger.info('update aliyun dns success')
    except Exception:
        logger.error("update aliyunv dns failed", exc_info=True)

    try:
        # update cloudflare
        logger.info("updata cloudflare dns")
        dns = CloudflareDns(flare["access_key_id"], flare["access_secret"], flare["region_id"])
        record = dns.DescribeDomainRecords(ali["domain"], ali["domain-a"])
        dns.UpdateDomainRecord(record, network_ip)
        logger.info('update  cloudflare dns success')
    except Exception:
        logger.error("update cloudflare dns failed", exc_info=True)
3.更新DNS

阿里云DNS更新代码,两个函数,第一个函数用于获取之前设置的DNS解析记录(主要拿到这条数据的id),第二个函数用于更新DNS。

# -*- coding:UTF-8 -*-
import json

from aliyunsdkalidns.request.v20150109.DescribeDomainRecordsRequest import DescribeDomainRecordsRequest
from aliyunsdkalidns.request.v20150109.UpdateDomainRecordRequest import UpdateDomainRecordRequest
from aliyunsdkcore.client import AcsClient


class AliCloudDns:
    def __init__(self, access_key_id, access_secret, region_id):
        self._client_ = AcsClient(access_key_id, access_secret, region_id)

    """
         search Records
        @:param main_domain 
        @:param child_domain
        @version: python3.x
        @author: wangjf
        @software: PyCharm
        @file: alicloud.py
        @time: 2019/7/8 20:40
    """

    def DescribeDomainRecords(self, main_domain, child_domain):
        request = DescribeDomainRecordsRequest()
        request.set_accept_format('json')
        request.set_DomainName(main_domain)
        response = self._client_.do_action_with_exception(request)
        result = json.loads(str(response, encoding='utf-8'))
        for record in result["DomainRecords"]["Record"]:
            if record["RR"] == child_domain:
                return record

    """
        update record by id
       @:param record 
       @:param ip 
       @version: python3.x
       @author: wangjf
       @software: PyCharm
       @file: alicloud.py
       @time: 2019/7/8 20:40
       """

    def UpdateDomainRecord(self, record, ip):
        # not need to update if not change
        if record['Value'] == ip:
            return ''

        request = UpdateDomainRecordRequest()
        request.set_accept_format('json')
        request.set_RecordId(record['RecordId'])
        request.set_RR(record['RR'])
        request.set_Type(record['Type'])
        request.set_Value(ip)
        response = self._client_.do_action_with_exception(request)
        return str(response, encoding='utf-8')

cloudflare的DNS更新代码,也是两个函数跟阿里云类似。

# -*- coding:UTF-8 -*-
import requests

import json


class CloudflareDns:
    def __init__(self, access_key_id, access_secret, zone_id):
        self.__access_key_id = access_key_id
        self.__access_secret = access_secret
        self.__zone_id = zone_id

    """
     search Records
    @:param main_domain 
    @:param child_domain
    @version: python3.x
    @author: wangjf
    @software: PyCharm
    @file: coludflare.py
    @time: 2020/2/8 21:40
    """

    def DescribeDomainRecords(self, main_domain, child_domain):
        url = "https://api.cloudflare.com/client/v4/zones/%s/dns_records?type=A&name=%s.%s" % (
            self.__zone_id, child_domain, main_domain)
        headers = {"X-Auth-Email": self.__access_key_id, "X-Auth-Key": self.__access_secret,
                   "Content-Type": "application/json"}
        response = requests.get(url, headers=headers).text
        result = json.loads(response)
        return result["result"][0]

    """
        update record by id
       @:param record 
       @:param ip 
       @version: python3.x
       @author: wangjf
       @software: PyCharm
       @file: coludflare.py
       @time: 2020/2/8 21:40
       """

    def UpdateDomainRecord(self, record, ip):
        # not need to update if not change
        if record['content'] == ip:
            return ''

        url = "https://api.cloudflare.com/client/v4/zones/%s/dns_records/%s" % (self.__zone_id, record['id'])
        headers = {"X-Auth-Email": self.__access_key_id, "X-Auth-Key": self.__access_secret,
                   "Content-Type": "application/json"}
        data = {"type": "A", "name": record['name'], "content": ip, "ttl": 600, "proxied": False}
        response = requests.put(url, json=data, headers=headers)
        return response.text


注意:修改exec_ddns.py中的ali和flare,配置对应的密钥或者登陆名密码。如果只使用其中一个,可以把另一个删掉,对应删掉代码。

下面是获取阿里云的AccessKey,在右上角,剩余步骤都是傻瓜式的,不介绍了。

ali = {
 “access_key_id”: “xxx”, # AccessKey
 “access_secret”: “xxx”, # AccessSecret
 “region_id”: “cn-hangzhou”, # 区域(默认就好)
 “domain”: “xxx.com”, # 你的域名
 “domain-a”: “home” # 你的域名前缀,比如www.baidu.com其中的www
 }

群晖使用Docker cli 群晖使用阿里云ddns_python_02


下面是cloudflare配置APIToken

flare = {
 “access_key_id”: “xxx@163.com”, # cloudflare登陆邮箱
 “access_secret”: “xxx”, # cloudflare登陆密码
 “region_id”: “xxx”, # 下面申请的api token
 “domain”: “xxx.com”, # 你的域名
 “domain-a”: “home” # 你的域名前缀,比如www.baidu.com其中的www
 }

群晖使用Docker cli 群晖使用阿里云ddns_群晖_03


群晖使用Docker cli 群晖使用阿里云ddns_python_04


群晖使用Docker cli 群晖使用阿里云ddns_json_05


群晖使用Docker cli 群晖使用阿里云ddns_群晖使用Docker cli_06

把代码拷贝到群晖服务器上,设置任务计划

我配置的每5分钟跑一次

群晖使用Docker cli 群晖使用阿里云ddns_python_07


群晖使用Docker cli 群晖使用阿里云ddns_ddns_08


群晖使用Docker cli 群晖使用阿里云ddns_python_09