背景:前段时间,朋友为了使用NAS服务,开通了电信的动态公网IP,也就是那种公网IP地址会定期变化,重启光猫也会导致IP变化,朋友的水星低端路由器支持花生壳DDNS,去花生壳官网申请服务后能够正常使用,但是隔一两天就会出现路由器花生壳服务掉线,重新连接不上的奇怪问题,久久不能解决,然后他找到了我,本着助人为乐的精神,我给他找到了一个替代方案。
简述:百度注册的域名可以提供域名解析API服务,但是需要工单申请API权限,申请后可以通过 python实现域名解析的动态更新,适合动态公网用户。
前期准备:
1.你需要拥有一个百度云注册的域名,并且已经备案(政策规定,国内未备案域名不能提供域名解析服务)。
2.去百度云个人中心>安全认证界面,创建Access Key,记录下这个Access Key和Secret Key。
3.去工单>域名服务>申请开通域名API>创建工单,贴上Access Key,申请开通域名解析API。
4.去域名管理界面设置一个域名解析A记录,具体设置教程自行百度。
一些说明:
百度云提供了域名解析API相关的帮助文档(域名服务-百度智能云),根据文档,其最重要的部分是生成认证Authorization字符串,具体步骤可以参考其网址(https://cloud.baidu.com/doc/Reference/s/Njwvz1wot),这里就不做具体阐述。其网站提供了一个例子,签名算法没有问题,但是使用的是python requests模块,经过实践并不能成功,原因在于requests会在请求数据header自动添加一些无关的头,但是这些头服务器会纳入鉴权计算,导致服务器鉴权Authorization字符串算出来和本地不一致。
代码部分:
import hmac
import time
import requests
import urllib.parse
import socket
import json
import os
class DDNS:
def __init__(self,url,AK,SK):
self.url=url
self.AK=AK
self.SK=SK
def getTime(self):#获取网络时间戳,用于鉴权
url="http://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp"
res=requests.get(url,timeout=5).text
res=json.loads(res)["data"]["t"]
return int(res)/1000
def getIP(self):#获取本地公网IP
url="http://pv.sohu.com/cityjson?ie=utf-8"
res=requests.get(url,timeout=5).text
res=res.split("=")[1].split(";")[0] #转换javascript为json格式
res=json.loads(res)
res=res["cip"]
return res
def enc(self,key,message):
h=hmac.new(key,message,digestmod="SHA256")
return h.hexdigest()
def post(self,URI,_data):
#填写参数
#获取UTC时间
utc = time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime(self.getTime()))
#POST字典1
data=json.dumps(_data)
#创建请求header字典
_headers={
"host":"bcd.baidubce.com",
"x-bce-date":utc,
"Content-Type":"application/json;charset=utf-8",
"Content-Length":str(len(data))
}
#格式化字典为http头标准格式
headers=""
for a,b in _headers.items():
headers+=a+":"+b+"\r\n"
#生成CanonicalHeaders
method = "POST"
CanonicalQueryString=""
CanonicalURI = urllib.parse.quote(URI)
result = []
for key,value in _headers.items():
tempStr = str(urllib.parse.quote(key.lower(),safe="")) + ":" + str(urllib.parse.quote(value,safe=""))
result.append(tempStr)
result.sort()
CanonicalHeaders = "\n".join(result)
#拼接得到CanonicalRequest
CanonicalRequest = method + "\n" + CanonicalURI + "\n" + CanonicalQueryString +"\n" + CanonicalHeaders
#计算signingKey
signingKey=self.enc(self.SK.encode(),b"bce-auth-v1/%b/%b/1800"%(self.AK.encode(),utc.encode()))
#计算signature
signature=self.enc(signingKey.encode(),CanonicalRequest.encode())
#生成认证Authorization
Authorization="bce-auth-v1/%s/%s/1800/content-length;content-type;host;x-bce-date/%s"%(self.AK,utc,signature)
#最后生成完整的http请求
http="POST %s HTTP/1.1\r\n%sAuthorization:%s\r\n\r\n"%(URI,headers,Authorization)+data
#建立socket链接,发送http数据
s=socket.socket()
s.connect(("bcd.baidubce.com",80))
s.send(http.encode())
res=b""
while True:
_res=s.recv(10240)
res+=_res
if b"chunked" not in res: #如果不是chunked,一次性读完退出
break
if _res[-5:]==b"0\r\n\r\n": #chunked标志,连续读取到标识符退出
break
s.close()
return res
def getID(self):
data={
'domain':self.url,
'pageNo':1,
'pageSize':100
}
url="/v1/domain/resolve/list"
res=self.post(url,data)
res=res.decode().split("\r\n{")[1].split("}\r\n")[0]
res="{"+res+"}"
res=json.loads(res)["result"]
recordId={}
for i in res:
recordId[i["domain"]]=(i["recordId"],i["rdata"])
return recordId
def SET(self,domain,ip):
recordId=self.getID()
self.urlIP=recordId[domain][1]
if ip!=recordId[domain][1]:
url="/v1/domain/resolve/edit"
data={
"domain" : domain,
"rdType" : "A",
"rdata" : ip,
"ttl" : 60,
"zoneName" : self.url,
"recordId" : recordId[domain][0]
}
res=self.post(url,data)
#print(res)
if __name__ == "__main__":
#参数设置
url="" #填写你的域名,如http://www.baidu.com,仅填写baidu.com
AK="" #Access Key 百度云控制台申请
SK="" #Secret Key 百度云控制台申请
sync=60 #检测间隔时间,建议60秒
#循环监控
ddns=DDNS(url,AK,SK)
first=True
jsq=sync
while True:
os.system("title DDNS服务已启动【%s秒后更新】"%jsq)
if jsq==0 or first==True:
try:
ddns.SET("@",ddns.getIP())
os.system("cls")
now=time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(ddns.getTime()))
print("---------百度域名DDNS信息---------\n\n域名:%s\n-----\n域名解析IP:%s\n本地公网IP:%s\n更新时间:%s"%(url,ddns.urlIP,ddns.getIP(),now))
except:
pass
jsq=sync
first=False
time.sleep(1)
jsq-=1