说明:
CloudFlare API接口:https://api.cloudflare.com/#dns-records-for-a-zone-list-dns-records
前不久发现CloudFlare的DNS接口又变化了,之前写的bash脚本无法在更新我的动态IP;去官网查了接口说明,发现接口不能返回子域名相关信息,返回的是主域名所有信息,自家的域名又不止这一个子域名。bash解决这个返回的json数据很麻烦,只能使用python来处理。个人现在是python新人,如果错误请留言。
下面的是python写的程序,处理步骤和之前脚本相同:
- 1、获取本地IP;
- 2、检查本地保存IP地址的文件,如果没有,就去获取DNS记录的IP;
- 3、如果没有DNS记录,通过API创建子域名解析;如果DNS记录不同,更新域名解析;
- 4、如果本地有IP地址记录,比较相同就不做处理。
Python:
#!/usr/bin/python3.9
import os
import json
import logging
import requests
from logging import handlers
# 日志输出
class Logger(object):
level_relations = {
'debug':logging.DEBUG,
'info':logging.INFO,
'warning':logging.WARNING,
'error':logging.ERROR,
'crit':logging.CRITICAL
}#日志级别关系映射
def __init__(self,filename,level='info',when='D',backCount=3,fmt='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'):
self.logger = logging.getLogger(filename)
format_str = logging.Formatter(fmt)#设置日志格式
self.logger.setLevel(self.level_relations.get(level))#设置日志级别
sh = logging.StreamHandler()#往屏幕上输出
sh.setFormatter(format_str) #设置屏幕上显示的格式
th = handlers.TimedRotatingFileHandler(filename=filename,when=when,backupCount=backCount,encoding='utf-8')#往文件里写入#指定间隔时间自动生成文件的处理器
th.setFormatter(format_str)#设置文件里写入的格式
self.logger.addHandler(sh) #把对象加到logger里
self.logger.addHandler(th)
# 查询子域名有没有创建DNS解析,返回id和ip,如果为空,返回-1
def Rget(ZoneName, ZoneId, Header):
global id, ClientIp, ip
url = 'https://api.cloudflare.com/client/v4/zones/' + ZoneId + '/dns_records'
# 用于get查询的body数据,content配置127.0.0.1
body = {
'match': 'any',
'type': 'A',
'content': '127.0.0.1',
'name': ZoneName,
'order': 'type',
'page': '1',
'per_page': '5',
'proxied': False,
'direction': 'desc'
}
# cloudflare请求方式get
response = requests.get(url, headers=Header, params=body)
# 转成dict进行查找处理
Text = json.loads(response.text)
domains = Text['result']
log.logger.info('DNS记录查询结果:' + str(domains))
# 查找子域名在dict的第几个位置,取出id和IP地址,用来update DNS记录
for num in range(len(domains)):
domain = domains[num]['name']
if domain == RecordName:
id = domains[num]['id']
ip = domains[num]['content']
break
else:
id = -1
ip = -1
return (id, ip)
# 更新DNS记录
def Rupdate(ZoneId, RecordName, id, ip, Header):
url = 'https://api.cloudflare.com/client/v4/zones/' + ZoneId + '/dns_records/' + id
#Header = json.loads(Header)
# 使用get查询到的数据,拼接body
body = {
'type': 'A',
'name': str(RecordName),
'content': ip,
'ttl': 1,
'proxied': False
}
body = json.dumps(body)
# cloudflare请求方式put
response = requests.put(url, headers=Header, data=body)
# 转成dict进行查找处理
Text = json.loads(response.text)
domains = Text['result']
log.logger.info('DNS记录修改结果:' + str(domains))
# 在返回的dict查找子域名,确认是否更新成功
domain = domains['name']
if domain == RecordName:
id = domains['id']
ip = domains['content']
log.logger.info('新的DNS记录更新成功,当前ID:' + str(id))
log.logger.info('新的DNS记录更新成功,当前IP:' + str(ip))
return (id, ip)
else:
log.logger.info('新的DNS记录更新失败')
# 创建DNS记录
def Rcreate(ZoneId, RecordName, ip, Header):
global id
url = 'https://api.cloudflare.com/client/v4/zones/' + ZoneId + '/dns_records'
# 使用get查询到的数据,拼接body
body = {
'type': 'A',
'name': str(RecordName),
'content': ip,
'ttl': 1,
'priority': 10,
'proxied': False
}
body = json.dumps(body)
# cloudflare请求方式post
response = requests.post(url, headers=Header, data=body)
# 转成dict进行查找处理
Text = json.loads(response.text)
domains = Text['result']
log.logger.info('创建新DNS纪录结果:' + str(domains))
# 在返回的dict查找子域名,确认是否创建成功
domain = domains['name']
if domain == RecordName:
id = domains['id']
ip = domains['content']
log.logger.info('新的DNS记录创建成功,当前ID:' + str(id))
log.logger.info('新的DNS记录创建成功,当前IP:' + str(ip))
return (id, ip)
else:
log.logger.info('新的DNS记录创建失败')
# 获取公网IP地址
def getip():
url = 'http://ipv4.icanhazip.com'
header = {
'Content-Type': 'application/json'
}
response = requests.get(url, headers=header)
# 去除空格和换行\n \r
text = response.text.replace(' ', '').replace('\n', '').replace('\r', '')
return text
# 将公网IP保存到本地文件,每次运行的时候,检查公网IP和本地记录是否一样,如果不一样,才执行API操作
def iprecord(newip):
# 如果IP记录文件存在,执行比较
if os.path.exists(ipfile):
with open(ipfile, 'r') as file:
localip = file.read()
# 如果公网IP和本地记录不一样,返回none
if str(newip) != str(localip):
log.logger.info('公网IP和本地记录不一样,本地记录IP:' + localip + ', 新IP:' + str(newip))
iprecord = 'none'
return iprecord
else:
log.logger.info('公网IP和本地记录一样,本地记录IP:' + localip + ', 新IP:' + str(newip))
else:
with open(ipfile, 'w') as file:
iprecord = 'none'
return iprecord
# 获取当前公网IP,读取本地记录的IP地址,比较是否一样,IP不同就通过API查询,如果公网IP和API查询结果不一样,就修改(没有记录就创建)DNS记录
def checkip(iprecord):
if iprecord == 'none':
id, ip = Rget(ZoneName, ZoneId, Header)
# 检查有没有DNS记录,没有记录就创建DNS记录,有记录比较现在的IP和记录IP是不是一样,不一样就更新DNS记录
if ip == newip:
log.logger.info('新的IP和DNS记录相同,没有修改DNS记录:' + str(newip))
with open(ipfile, 'w') as file:
file.write(ip)
elif ip == -1: # 如果IP为空,创建新的DNS记录
id, ip = Rcreate(ZoneId, RecordName, newip, Header)
log.logger.info('当前DNS记录的ID是:' + str(id))
log.logger.info('当前DNS记录的IP是:' + str(ip))
with open(ipfile, 'w') as file:
file.write(ip)
elif ip != newip and ip != -1: # 如果IP不为空,并且和当前IP不同,更新DNS记录
id, ip = Rupdate(ZoneId, RecordName, id, newip, Header)
log.logger.info('当前DNS记录的ID是:' + str(id))
log.logger.info('当前DNS记录的IP是:' + str(ip))
with open(ipfile, 'w') as file:
file.write(ip)
if __name__ == '__main__':
# 本地IP保存文件
#ipfile = '/tmp/ip.log' # linux
ipfile = 'ip.log' # windows测试
# 配置日志文件
#logfile = '/var/log/cloudflare-py.log' # linux
logfile = 'cloudflare-py.log' # windows测试
# 日志文件输出和日志等级
log = Logger(logfile, level='info')
# 主域名zoneid
ZoneId = 'xxxxx....xxxxx'
# 令牌,需有dns添加,编辑,修改权限
DnsToken = 'xxxxx....xxxxx'
# Header数据,注意Bearer后面有空格,拼接格式Bearer xxxxx....xxxxx
Header = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + DnsToken
}
# 主域名和子域名
ZoneName = 'domain.com'
RecordName= 'naas.domain.com'
# 获取当前最新IP
newip = getip()
# 比较最新获取的IP和本地IP记录,如果不同,开始通过API接口进行比较,创建,更新
iprecord = iprecord(newip)
# 比较最新获取的IP和DNS记录,处理更新、创建
checkip(iprecord)