网上关于DDNS解析的介绍已经很多了,我这里主要列举通过阿里云和Cloudflare进行解析。使用两个解析的原因是阿里云的域名必须要备案,不然会被阿里云封掉(阿里云解析的域名如果解析的IP不是阿里云服务器的,还会提示要求使用阿里云服务器。)。而Cloudflare不用备案,可以直接用。在阿里云申请的域名可以在阿里云上配置为通过Cloudflare解析。当然不只Cloudflare,其实有很多提供DNS解析服务的厂商都提供了DNS的API,就不一一列举了。
如果买的群晖服务器的话,本身群晖会送一个域名,也会自动做DDNS解析,不需要自己折腾。如果没有群晖的服务器或者想用自己的已经申请的域名,就需要自己做DDNS解析。DDNS解析说白了就是调用第三方提供的更新DNS的API。
步骤如下:
- 获取当前的公网IP;
- 对比当前公网IP是否变化;
- 若发生变化则更新DNS解析。
下面是具体实现过程,我通过python来实现。
1.获取当前的公网IP
获取公网ip目前我有四种方式:
- 通过www.ifconfig.me获取,因为群晖本身也是linux环境,代码跑在群晖上,可以直接curl www.ifconfig.me -s得到公网ip。不过这个网站好像是国外的,有时候访问很慢,可能会获取超时,这时候可以尝试其他方式。
- 通过200019.ip138.com获取,这是国内的获取ip,百度上搜ip就是调用的这个网站的。优点速度快,缺点要自己分析页面提取ip。
- 自己搭一个nginx服务器,然后配置nginx获取真实IP。优点不会受制于别人,获取方便。缺点是有个服务器,要自己部署个程序。
- 自己写代码解析获取真实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 subprocessimport requestsfrom util.tonggu_logger import TongguLoggerlogger = 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 ipresult = 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 = rowselse:raise Exception('get ip from www.ifconfig.me failed')return ipexcept 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 ipexcept 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 CloudflareDnsimport sys
from util.tonggu_logger import TongguLogger
import util.path
import pathliblogger = 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 filewith file.open('w') as f:f.writelines(content)return Trueif __name__ == "__main__":try:network_ip = get_network_ip()if network_ip is None:raise Exception("get ip failed")# check ip is changedif save_content('/tmp', 'ip', network_ip) == False:sys.exit(2)except Exception:logger.error("get ip failed", exc_info=True)sys.exit(2)# update dnstry:# update alicloudlogger.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 cloudflarelogger.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 jsonfrom aliyunsdkalidns.request.v20150109.DescribeDomainRecordsRequest import DescribeDomainRecordsRequest
from aliyunsdkalidns.request.v20150109.UpdateDomainRecordRequest import UpdateDomainRecordRequest
from aliyunsdkcore.client import AcsClientclass 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 changeif 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 requestsimport jsonclass CloudflareDns:def __init__(self, access_key_id, access_secret, zone_id):self.__access_key_id = access_key_idself.__access_secret = access_secretself.__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).textresult = 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 changeif 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
}
下面是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
}
把代码拷贝到群晖服务器上,设置任务计划
我配置的每5分钟跑一次