CloudFlare API通过Python更新DDNS

管理员 2021年12月18日 1,196次浏览

说明:

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)