基于Python+腾讯云DNSPod动态域名解析(DDNS)的家庭电脑公网访问解决方案

Original
2021-02-01 / 0 评论 / 10 阅读 / 正在检测是否收录...

引言

DDNSDynamic Domain Name Server(动态域名服务),将动态IP地址映射到一个固定的域名解析服务上。

通常一个域名对应的IP地址是固定的,例如blog.899988.xyz对应120.77.144.68,云服务器IP是固定不变的。如果使用DDNS,那么域名所对应的IP是可以动态变化的。那这有啥用呢?

需求分析

昨天偶然发现家里光纤有公网IP(真香),于是想着把云服务器上跑的一些服务部署到家里电脑上,通过路由器的
不分析了,没啥好分析的,有需求的直接拿代码吧,写文章的活太难了

效果

光纤重拨后,运营商重新分配IP,脚本自动更新DNSPod上的解析记录的IP。
show.png

DDNSmain.py

import requests
import getIP
import time
import datetime


class DNSPod:
    apiDomain = 'https://dnsapi.cn'

    API = {
        'Record': {
            'List': f'{apiDomain}/Record.List',
            'Ddns': f'{apiDomain}/Record.Ddns',
        }
    }

    def __init__(self, id, token):
        # API Token https://docs.dnspod.cn/account/5f2d466de8320f1a740d9ff3/
        self.loginToken = f'{id},{token}'

        # 公共参数 https://docs.dnspod.cn/api/5f561f9ee75cf42d25bf6720/
        self.baseData = {
            'login_token': self.loginToken,
            'format': 'json',
            'lang': 'cn'
        }
        self.session = requests.session()

        # API开发规范之User-Agent https://docs.dnspod.cn/api/5f55993d8ae73e11c5b01ce6/
        self.session.headers.update({
            'User-Agent': f"{conf['scriptName']}/{conf['scriptVersion']}({conf['email']})"
        })

    # 获取解析记录 https://docs.dnspod.cn/api/5f562ae4e75cf42d25bf689e/
    def getRecordList(self, offset, length):
        data = {
            'domain': conf['domain'],
            'offset': offset,
            'length': length
        }
        data.update(self.baseData)

        try:
            req = self.session.post(self.API['Record']['List'], data, timeout=1)

        except requests.exceptions.ConnectTimeout:
            echo(f'GetRecordList exception: ConnectTimeout.')

        except requests.exceptions.ReadTimeout:
            echo(f'GetRecordList exception: ReadTimeout.')

        except Exception as e:
            echo(f'GetRecordList exception:{e}')

        else:
            try:
                res = req.json()

            except Exception as e:
                echo(f'GetRecordList exception:{e} resp:{req.text}')

            else:
                return res['records']

    # 更新动态DNS记录 https://docs.dnspod.cn/api/5f562b21e75cf42d25bf68b6/
    def updateRecord(self, recordId, ip):
        data = {
            'domain': conf['domain'],
            'record_id': recordId,
            'sub_domain': conf['name'],
            'record_line_id': 0,
            'value': ip
        }
        data.update(self.baseData)

        try:
            req = self.session.post(self.API['Record']['Ddns'], data, timeout=1)

        except requests.exceptions.ConnectTimeout:
            echo(f'UpdateRecord exception: ConnectTimeout.')

        except requests.exceptions.ReadTimeout:
            echo(f'UpdateRecord exception: ReadTimeout.')

        except Exception as e:
            echo(f'UpdateRecord exception:{e}')

        else:
            try:
                res = req.json()

            except Exception as e:
                echo(f'UpdateRecord exception:{e} resp:{req.text}')

            else:
                code = res['status']['code']
                name = res.get('record', None).get('name', None)
                value = res.get('record', None).get('value', None)
                echo(f'Update record success! code:[{code}] name:[{name}] ip:[{value}]')


def echo(data):
    now = datetime.datetime.now()
    print(f'{data} {now}')


def main():
    # 获取DNSPod上的解析列表,通过偏移(offset)和数量(length)可精准获取到指定的一条记录
    # 详情见开发文档参数描述 https://docs.dnspod.cn/api/5f562ae4e75cf42d25bf689e/
    record = dnspod.getRecordList(4, 1)

    if record:
        # DNSPod上解析记录的IP
        nowIP = record[0]['value']

        # 本机公网IP
        localIP = getIP.GetIP.getIP()

        # 当获取本机IP失败时,将IP指向云服务器IP以显示临时页面,保证不会无响应
        if not localIP:
            localIP = conf['defaultIP']
            echo(f'Failed to get LocalIP, default IP:[{localIP}]')

        echo(f'DNSPod IP:[{nowIP}] Local IP:[{localIP}]')

        if nowIP != localIP:
            echo(f'IP does not match, start update.')
            # DNSPod记录的IP与本机IP不同时,更新记录IP
            dnspod.updateRecord(conf['recordId'], localIP)
    else:
        echo('Failed to getRecordList.')


if __name__ == '__main__':
    # 申请密钥 https://docs.dnspod.cn/account/5f2d466de8320f1a740d9ff3/
    # 密钥管理控制台 https://console.dnspod.cn/account/token
    # conf配置根据个人实际数据修改
    conf = {
        'id': 123456, # 密钥ID
        'token': '78****************************00', # 密钥Token
        'defaultIP': 'xxx.xxx.xxx.xxx', # 当LocalIP获取失败时,临时将域名解析到云服务器,云服务器将返回用户一个临时页面告知用户IP正在解析。
        'name': 'ddns', # 二级域名
        'recordId': 123456789, # 此条解析的recordId,可通过getRecordList获取
        'domain': 'example.com', # 要解析的域名

        # API开发规范之User-Agent https://docs.dnspod.cn/api/5f55993d8ae73e11c5b01ce6/
        'email': 'xx@xx.xx',
        'scriptName': 'Original DDNS',
        'scriptVersion': '1.0.0'
    }

    dnspod = DNSPod(conf['id'], conf['token'])

    while True:
        main()
        # 两秒监测一次
        # API调用频次说明 https://docs.dnspod.cn/api/5f561fcfe75cf42d25bf672a/
        time.sleep(2)

GetIP

import requests


class GetIP:
    API = [
        'https://api.ip138.com/ip/',
    ]

    @classmethod
    def getIP(self):
        ip = None

        try:
            req = requests.get(self.API[0], {
                'token': '54****************************76' # ip138用户token https://user.ip138.com/ip/doc/
            }, timeout=1)

        except Exception as e:
            print(f'request exception:{e}')

        else:
            if req.status_code == 200:
                try:
                    res = req.json()

                except Exception as e:
                    print(f'decode exception:{e}')

                else:
                    if res['ret'] == 'ok':
                        ip = res['ip']

                    else:
                        print(req.text)
            else:
                print(f'response code:{req.status_code}')

        return ip
2

评论 (0)

取消