闲话休提,我们先上拓扑:
嗯~ o( ̄▽ ̄)o,画图是在线画的,地址分享一下,表现出我的厚道:
OK,分析一下我们需求:
1.我们需要一个公网IP地址,那么我的无线路由器需要PPPoE拨号
2.域名,目前国内买域名是越来越简单了,因为大厂各种收购,所以就在阿里买了个域名,10年79元很划算吧,自己用所以没买com啊net啊这种。
3.这个时候发现,PPPoE拨号上去的地址,有时候会变,那么我们就需要DDNS的功能,当然使用花生壳的一些域名或者群晖的域名,也是可以在路由器或NAS上完成DDNS功能的,可惜了你用的自己买的域名就需要自己搞一个程序给你定时去完成该功能,这也是为什么本次分享的意义。或许有更优解。请教我谢谢~~~~
第一个需求:
我们只需要购买电信的宽带,在师傅上门安装的时候,跟他说我要桥接,路由器PPPoE获取地址。 嗯~ o( ̄▽ ̄)o就可以了,他就会找后台小姐姐帮你去改模式。他要是不愿意,嗯,投诉他就行了。
看下面图,外网IP这边可以相应的获取到一个公网的IP,OK,完成第一个问题:
第二个需求:
这部分就不多说了,上阿里云直接选域名,购买就行了
这里分享一下购买的时候一个小细节,就是更多价格里有的10年的会特别便宜,购买的时候可以关注一下:
第三个需求:
切入正题,那就是如何用python实现DDNS功能。
因为我家里有一个常开的NAS,我觉得这里可以用个树莓派也可以,常挂在路由器后面运行个python程序就行。
如何实现真个功能呢? 那就分析一下代码需要实现的功能:
1.获取当前公网IP地址
2.查询当前阿里账号下的域名
3.根据域名查询当前是否有域名解析记录
4.根据判断添加或者更新域名解析
那么下面进入代码(这里不过多赘述,具体看代码注解):
import urllib
from aliyunsdkcore.client import AcsClient
from aliyunsdkalidns.request.v20150109 import DescribeDomainsRequest, DescribeDomainRecordsRequest, \
UpdateDomainRecordRequest, AddDomainRecordRequest
import time
import json
#获取公网IP地址
def get_internet_ip():
with urllib.request.urlopen('http://www.3322.org/dyndns/getip') as response:
html = response.read()
ip = str(html, encoding='utf-8').replace("\n", "")
return ip
#获取当前域名下的记录内容,得到的是一个json格式的信息,主要是看TotalCount和record的列表
def Describe_SubDomain_Records():
request = DescribeDomainRecordsRequest.DescribeDomainRecordsRequest()
request.set_accept_format('json')
#request.set_Type(record_type)
#request.set_SubDomain(subdomain)
request.set_DomainName(list_domain())
response = client.do_action_with_exception(request)
response = str(response, encoding='utf-8')
relsult = json.loads(response)
print(list_domain() + ':' + json.dumps(relsult))
return relsult
#获取你的账户下的域名列表
def list_domain():
DomainList = DescribeDomainsRequest.DescribeDomainsRequest()
DomainList.set_accept_format('json')
DNSListJson = json.loads(client.do_action_with_exception(DomainList))
for i in DNSListJson['Domains']['Domain']:
print(i['DomainName'])
return i['DomainName']
print(DNSListJson)
#添加记录,不过如果以前就有配置过记录,这个方法应该用不到
def add_record(client, priority, ttl, record_type, value, rr, domainname):
request = AddDomainRecordRequest.AddDomainRecordRequest()
request.set_accept_format('json')
request.set_Priority(priority)
request.set_TTL(ttl)
request.set_Value(value)
request.set_Type(record_type)
request.set_RR(rr)
request.set_DomainName(domainname)
response = client.do_action_with_exception(request)
response = str(response, encoding='utf-8')
relsult = json.loads(response)
return relsult
#更新记录,这里就是把新IP更新进去
def update_record(client, priority, ttl, record_type, value, rr, record_id):
request = UpdateDomainRecordRequest.UpdateDomainRecordRequest()
request.set_accept_format('json')
request.set_Priority(priority)
request.set_TTL(ttl)
request.set_Value(value)
request.set_Type(record_type)
request.set_RR(rr)
request.set_RecordId(record_id)
response = client.do_action_with_exception(request)
response = str(response, encoding='utf-8')
return response
def main():
ip = get_internet_ip()
print('now IP: ' + ip)
with open("ip", 'r') as f:
old_ip = f.read()
if ip == old_ip:
print("no update" + "\nnew_ip:" + ip + "\nold_ip:" + old_ip)
else:
try:
# print("update"+"\nnew_ip:"+ip+"\nold_ip:"+old_ip)
with open("ip", 'w') as f_w:
f_w.write(ip)
des_relsult = Describe_SubDomain_Records()
#print(des_relsult)
# 判断子域名解析记录查询结果,TotalCount为0表示不存在这个子域名的解析记录,需要新增一个
if des_relsult["TotalCount"] == 0:
add_relsult = add_record(client, "5", "600", "A", ip, "*", "programers.ltd")
record_id = add_relsult["RecordId"]
print("域名解析新增成功!")
# 判断子域名解析记录查询结果,TotalCount为1表示存在这个子域名的解析记录,需要更新解析记录,更新记录需要用到RecordId,这个在查询函数中有返回des_relsult["DomainRecords"]["Record"][0]["RecordId"]
elif des_relsult["TotalCount"] == 1:
#这里很清楚,就是更新第一条记录否则这里就是["0"]了
record_id = des_relsult["DomainRecords"]["Record"][0]["RecordId"]
update_record(client, "5", "600", "A", ip, "*", record_id)
print("域名解析更新成功!")
else:
record_id = 0
print("存在两个子域名解析记录值,请核查删除后再操作!")
#记录下更新好的recordid,对比是否冲突
path = 'RecordId'
with open(path, 'w') as f_w1:
f_w1.write(record_id)
except Exception as result:
print('错误: %s' % result)
# 这种写法可以将报错原因显示出来,并且程序不会终止,会安安稳稳的运行
pass
if __name__ == '__main__':
while True:
client = AcsClient('<accessKeyId>', '<accessSecret>', 'cn-hangzhou')
#这里你肯定要问这个key哪里来的,请看参考链接中的创建RAM用户,这里会有方法,创建好用户后,一定要先把两个值记录下来,后面好像看不到了,并且请赋予"DNS云解析的权限"
main()
print(time.strftime('%Y-%m-%d %X', time.localtime()))
time.sleep(180)
接下来就是在NAS或者树莓派部署服务了:
1.安装Python3
https://www.python.org/ 自行下载安装,官网下的贼慢。
2.安装pip
$ curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py # 下载安装脚本
$ sudo python3 get-pip.py # 运行安装脚本
3.安装setuptools
这里安装有些不一样的是NAS的pip3默认路径有点问题,所以在它的root的环境变量中没有pip3的路径,所以我这里是需要写全的。
平常的Linux可以直接执行pip3 install ****的。
root@DS218plus:~# /volume1/@appstore/py3k/usr/local/bin/pip3 install setuptools
Collecting setuptools
Downloading setuptools-47.3.1-py3-none-any.whl (582 kB)
|████████████████████████████████| 582 kB 34 kB/s
Installing collected packages: setuptools
WARNING: The scripts easy_install and easy_install-3.5 are installed in '/volume1/@appstore/py3k/usr/local/bin' which is not on PATH.
Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
Successfully installed setuptools-47.3.1
4.安装ali的SDK
这里就不要再用自带源了,真心慢,说不定还报错。建议咱们下阿里的SDK就用阿里的源。
额。。。有点像绕口令……
可以参考下面链接中的pip安装源修改
root@DS218plus:~# /volume1/@appstore/py3k/usr/local/bin/pip3 install aliyun-python-sdk-core -i https://mirrors.aliyun.com/pypi/simple/
Looking in indexes: https://mirrors.aliyun.com/pypi/simple/
Collecting aliyun-python-sdk-core
Downloading https://mirrors.aliyun.com/pypi/packages/85/f3/a4d18d44d037f2c570fc0f0cb5f954785bf1763861c5bbffb1e14aab2bb8/aliyun-python-sdk-core-2.13.19.tar.gz (433 kB)
|████████████████████████████████| 433 kB 12.1 MB/s
Collecting jmespath<1.0.0,>=0.9.3
Downloading https://mirrors.aliyun.com/pypi/packages/07/cb/5f001272b6faeb23c1c9e0acc04d48eaaf5c862c17709d20e3469c6e0139/jmespath-0.10.0-py2.py3-none-any.whl (24 kB)
Collecting pycryptodome>=3.4.7
Downloading https://mirrors.aliyun.com/pypi/packages/bf/7d/6b6494d91534b470f8045b9be013f1ba63eb84333da27d44b4efd4ae3e34/pycryptodome-3.9.8-cp35-cp35m-manylinux1_x86_64.whl (13.7 MB)
|████████████████████████████████| 13.7 MB 32.4 MB/s
Building wheels for collected packages: aliyun-python-sdk-core
Building wheel for aliyun-python-sdk-core (setup.py) ... done
Created wheel for aliyun-python-sdk-core: filename=aliyun_python_sdk_core-2.13.19-py3-none-any.whl size=526717 sha256=af1460e5fa6d4f286cdd07691b03af553b09008a5a8c3e1f4262d4068ebf3167
Stored in directory: /root/.cache/pip/wheels/71/9e/25/16ceb9310c0a85893d5965a5ba245382159e04b880b25218ad
Successfully built aliyun-python-sdk-core
Installing collected packages: jmespath, pycryptodome, aliyun-python-sdk-core
Successfully installed aliyun-python-sdk-core-2.13.19 jmespath-0.10.0 pycryptodome-3.9.8
root@DS218plus:~#
root@DS218plus:~# /volume1/@appstore/py3k/usr/local/bin/pip3 install aliyun-python-sdk-alidns -i https://mirrors.aliyun.com/pypi/simple/
Looking in indexes: https://mirrors.aliyun.com/pypi/simple/
Collecting aliyun-python-sdk-alidns
Downloading https://mirrors.aliyun.com/pypi/packages/c9/33/ccb936224ba07e5fceec7df491ff436218d4b569d748473f2294c45f1ac0/aliyun-python-sdk-alidns-2.0.18.tar.gz (12 kB)
Requirement already satisfied: aliyun-python-sdk-core>=2.11.5 in /volume1/@appstore/py3k/usr/local/lib/python3.5/site-packages (from aliyun-python-sdk-alidns) (2.13.19)
Requirement already satisfied: pycryptodome>=3.4.7 in /volume1/@appstore/py3k/usr/local/lib/python3.5/site-packages (from aliyun-python-sdk-core>=2.11.5->aliyun-python-sdk-alidns) (3.9.8)
Requirement already satisfied: jmespath<1.0.0,>=0.9.3 in /volume1/@appstore/py3k/usr/local/lib/python3.5/site-packages (from aliyun-python-sdk-core>=2.11.5->aliyun-python-sdk-alidns) (0.10.0)
Building wheels for collected packages: aliyun-python-sdk-alidns
Building wheel for aliyun-python-sdk-alidns (setup.py) ... done
Created wheel for aliyun-python-sdk-alidns: filename=aliyun_python_sdk_alidns-2.0.18-py3-none-any.whl size=97707 sha256=243ff06c102a7048b5646994bcc008f11285e8ec6c0ae945db4d0cd0076f555a
Stored in directory: /root/.cache/pip/wheels/e6/71/7f/dab52549224c09df054463023bb72367651fd799fe10d5f8bf
Successfully built aliyun-python-sdk-alidns
Installing collected packages: aliyun-python-sdk-alidns
Successfully installed aliyun-python-sdk-alidns-2.0.18
root@DS218plus:~#
ok可以先测试一下好不好使了:
运行:python3 ddns.py
再把路由器拨号处,点击断开,再重新拨号,就可以获得一个新的地址
问题:
错误: the JSON object must be str, not 'bytes'
检查了一下代码,对不起各位
在list_domain()方法中
json.loads(client.do_action_with_exception(DomainList).decode())
这里需要加上decode(),这就是报错的原因。
root@DS218plus:~/ddns/DDNS4NAS# python3 ddns.py
now IP: XX.XX.XX.XX
example.ltd
example.ltd
example.ltd:{"PageNumber": 1, "PageSize": 20, "TotalCount": 1, "RequestId": "E#####-94F7-4B25-A91D-A2#####4976", "DomainRecords": {"Record": [{"DomainName": "example.ltd", "RR": "*", "Remark": "218plus", "Locked": false, "TTL": 600, "Value": "XX.22.22.22", "Weight": 1, "Type": "A", "Status": "ENABLE", "Line": "default", "RecordId": "1##############"}]}}
域名解析更新成功!
2020-06-27 22:14:14
__________________________________________________
now IP: XX.XX.XX.XX
no update
new_ip:XX.XX.XX.XX
old_ip:XX.XX.XX.XX
2020-06-27 22:14:32
__________________________________________________
至此部署测试完成,
下面我们就需要在后台运行这个程序就可以了,这里建议使用nohup:
nohup python3 -u ddns.py > /var/services/homes/test/DDNS.log 2>&1 &
来吧,一条查看指令结束这篇文章:
tail -f /var/services/homes/test/DDNS.log
参考链接:
官方论坛提供的方案:https://yq.aliyun.com/articles/702552
Python的Ali SDK: https://develop.aliyun.com/tools/sdk?#/python
pip安装源修改: https://blog.csdn.net/unicorn_mitnick/article/details/89738544
创建RAM用户:https://help.aliyun.com/document_detail/47664.html