网上关于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 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
}
下面是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分钟跑一次