通过一个域名,借助IPV6免费远程访问自己家里的设备

方案已实现,可抄作业

前提:

  • 有一个域名(我的是腾讯云的)
  • 毕竟说的再多,也需要有一个可以远程跳转的地方

实现思路:

  • 借助IPV6,可以实现短暂的公网访问
  • 通过代码自动配置腾讯云的域名解析,实现持久的访问

注意:此方案仅用于个人远程访问家用设备,不可用于网站公开访问,不合法合规。

问:获取到IPV6了却在公网无法访问怎么办?

答:进行光猫转桥接

问:不会光猫转桥接怎么办

答:淘宝下单找人帮你搞,几十块钱

好了,现在已经能够在公网进行访问了,下一步,添加代码可以控制的云解析ID和key

右上角,用户信息里面,访问管理

左边,API秘钥管理,创建,保存

然后就是把ID和秘钥粘贴到代码最下面的秘钥的位置,运行,就可以了。

代码如下:

python 复制代码
import requests
import json
import time
import socket
import logging
import os
from typing import Optional, List
from logging.handlers import TimedRotatingFileHandler
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.dnspod.v20210323 import dnspod_client, models

# -------------------------- 日志配置(按天分割+指定目录)--------------------------


def init_logger():
    # 创建log文件夹
    log_dir = "log"
    os.makedirs(log_dir, exist_ok=True)

    # 文件日志处理器
    log_file = os.path.join(log_dir, "ddns.log")
    file_handler = TimedRotatingFileHandler(
        filename=log_file,
        when='D',
        interval=1,
        backupCount=30,
        encoding='utf-8'
    )
    file_handler.suffix = "%Y-%m-%d"

    # 控制台日志处理器
    console_handler = logging.StreamHandler()

    # 日志格式
    log_format = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    file_handler.setFormatter(log_format)
    console_handler.setFormatter(log_format)

    # 初始化日志器
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)
    logger.addHandler(file_handler)
    logger.addHandler(console_handler)

    return logger


# 初始化日志
logger = init_logger()


class TencentCloudDDNS:
    def __init__(self, secret_id: str, secret_key: str, domain: str):
        """初始化腾讯云DDNS客户端"""
        self.secret_id = secret_id
        self.secret_key = secret_key
        self.domain = domain
        self.last_ipv6 = None

        # 初始化SDK客户端
        cred = credential.Credential(secret_id, secret_key)
        httpProfile = HttpProfile()
        httpProfile.endpoint = "dnspod.tencentcloudapi.com"

        clientProfile = ClientProfile()
        clientProfile.httpProfile = httpProfile
        self.client = dnspod_client.DnspodClient(cred, "", clientProfile)

    def get_public_ipv6(self) -> Optional[str]:
        """获取本地公网IPv6地址"""
        try:
            # 方法1: 通过icanhazip获取
            response = requests.get('https://icanhazip.com', timeout=5)
            ipv6 = response.text.strip()

            # 验证IPv6格式
            try:
                socket.inet_pton(socket.AF_INET6, ipv6)
                return ipv6
            except socket.error:
                logger.warning(f"获取的IPv6格式无效: {ipv6},尝试备用接口")

                # 方法2: 通过ipify获取
                response = requests.get('https://api64.ipify.org', timeout=5)
                ipv6 = response.text.strip()
                socket.inet_pton(socket.AF_INET6, ipv6)
                return ipv6
        except socket.error as e:
            logger.error(f"获取的IPv6格式无效: {e}")
            return None
        except requests.exceptions.RequestException as e:
            logger.error(f"网络请求失败: {e}")
            return None
        except Exception as e:
            logger.error(f"获取IPv6地址失败: {str(e)}")
            return None

    def get_all_aaaa_records(self) -> List[dict]:
        """获取域名的所有AAAA记录"""
        try:
            req = models.DescribeRecordListRequest()
            params = {
                "Domain": self.domain,
                "RecordType": "AAAA"
            }
            req.from_json_string(json.dumps(params))

            resp = self.client.DescribeRecordList(req)
            records = json.loads(resp.to_json_string()).get("RecordList", [])
            logger.info(f"获取到 {len(records)} 条AAAA记录")
            return records
        except Exception as e:
            logger.error(f"获取AAAA记录失败: {str(e)}")
            return []

    def update_record(self, sub_domain: str, record_id: int, ipv6: str) -> bool:
        """更新单条域名记录"""
        try:
            req = models.ModifyRecordRequest()
            params = {
                "Domain": self.domain,
                "RecordId": record_id,
                "SubDomain": sub_domain,
                "RecordType": "AAAA",
                "RecordLine": "默认",
                "Value": ipv6,
                "TTL": 600
            }
            req.from_json_string(json.dumps(params))

            self.client.ModifyRecord(req)
            logger.info(f"成功更新记录: {sub_domain}.{self.domain} -> {ipv6}")
            return True
        except Exception as e:
            logger.error(f"更新记录 {sub_domain}.{self.domain} 失败: {str(e)}")
            return False

    def check_and_update(self) -> bool:
        """检查IPv6变化并更新"""
        current_ipv6 = self.get_public_ipv6()
        if not current_ipv6:
            logger.error("无法获取有效公网IPv6地址,跳过本次检查")
            return False

        logger.info(f"当前公网IPv6地址: {current_ipv6}")

        if self.last_ipv6 and current_ipv6 == self.last_ipv6:
            logger.info("IPv6地址未变化,无需更新")
            return True

        records = self.get_all_aaaa_records()
        if not records:
            logger.error("未获取到任何AAAA记录,无法更新")
            return False

        update_results = []
        for record in records:
            sub_domain = record.get("Name")
            record_id = record.get("RecordId")

            if not sub_domain or record_id is None:
                logger.warning(f"跳过无效记录: {record}")
                continue

            try:
                record_id = int(record_id)
            except (ValueError, TypeError):
                logger.warning(f"无效的 RecordId: {record_id},跳过该记录")
                continue

            logger.info(
                f"开始处理记录: {sub_domain}.{self.domain} (ID: {record_id})")
            result = self.update_record(sub_domain, record_id, current_ipv6)
            update_results.append(result)

        if all(update_results):
            logger.info("所有AAAA记录更新成功,同步last_ipv6记录")
            self.last_ipv6 = current_ipv6
            return True
        else:
            logger.error("部分/全部记录更新失败,保留原last_ipv6记录(下次将重试)")
            return False

    def monitor(self, interval: int = 60) -> None:
        """持续监控"""
        logger.info(f"DDNS监控启动,检查间隔: {interval}秒")

        # 首次运行立即检查
        if not self.check_and_update():
            logger.warning("首次检查更新失败,将在下次间隔后重试")

        # 循环监控
        try:
            while True:
                time.sleep(interval)
                self.check_and_update()
        except KeyboardInterrupt:
            logger.info("程序被用户手动中断")
        except Exception as e:
            logger.error(f"监控循环异常终止: {str(e)}")


if __name__ == "__main__":
    # -------------------------- 配置信息(请替换为你的实际信息)--------------------------
    SECRET_ID = ""  # 你的腾讯云SECRET_ID
    SECRET_KEY = ""    # 你的腾讯云SECRET_KEY
    DOMAIN = ""                                # 你的主域名
    CHECK_INTERVAL = 60                                 # 检查间隔(秒)

    # 启动DDNS监控
    ddns_client = TencentCloudDDNS(SECRET_ID, SECRET_KEY, DOMAIN)
    ddns_client.monitor(interval=CHECK_INTERVAL)
相关推荐
muxin-始终如一1 小时前
Semaphore 使用及原理详解
java·开发语言·python
izx8881 小时前
JavaScript 面向对象编程(OOP):从原始模式到原型继承
前端·javascript
圆弧YH1 小时前
edge + google
前端·edge
b***59431 小时前
【Nginx 】Nginx 部署前端 vue 项目
前端·vue.js·nginx
前端fighter1 小时前
全栈项目:旅游攻略系统
前端·后端·源码
.格子衫.1 小时前
027动态规划之矩阵DP——算法备赛
算法·矩阵·动态规划
nju_spy1 小时前
力扣每日一题(11.10-11.29)0-1 和 k 整除系列
python·算法·leetcode·前缀和·单调栈·最大公约数·0-1背包
我血条子呢1 小时前
【Vite】离线打包@iconify/vue的图标
前端·javascript·vue.js
roman_日积跬步-终至千里1 小时前
【模式识别与机器学习(8)】主要算法与技术(下篇:高级模型与集成方法)之 元学习
学习·算法·机器学习