方案已实现,可抄作业
前提:
- 有一个域名(我的是腾讯云的)
- 毕竟说的再多,也需要有一个可以远程跳转的地方
实现思路:
- 借助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)