Python中如何利用ISP分配的IPv6前缀和自定义后缀实现动态DNS(DDNS)

openwrt 有无这样的工具?

需求是这样的,内网有多台设备需要公网访问。每台设备都有自己的公网 ipv6 地址。

可以实现多 IPv6 地址 AAAA 动态解析到指定子域名,dsm.xxxx.xx | esxi.xxxx.xx | win2018.xxxx.xx |等等


Python中如何利用ISP分配的IPv6前缀和自定义后缀实现动态DNS(DDNS)
9 回复

难道不是保持不要断网即可么


#!/usr/bin/env python3
"""
IPv6动态DNS更新脚本
通过解析ISP分配的IPv6前缀 + 自定义后缀生成完整地址并更新DNS记录
"""

import subprocess
import socket
import requests
import time
import logging
from typing import Optional, Tuple

# 配置区域
INTERFACE = "eth0"  # 你的网络接口名
CUSTOM_SUFFIX = "::1"  # 你的自定义后缀,例如 "::1" 或 "::100"
DNS_PROVIDER = "cloudflare"  # 支持的DNS服务商:cloudflare, godaddy, route53
API_TOKEN = "your_api_token_here"  # DNS服务商的API令牌
ZONE_ID = "your_zone_id"  # CloudFlare区域ID
RECORD_NAME = "ipv6.yourdomain.com"  # 要更新的DNS记录名
RECORD_ID = "existing_record_id"  # 现有DNS记录的ID(CloudFlare需要)
CHECK_INTERVAL = 300  # 检查间隔(秒)

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

def get_ipv6_prefix(interface: str) -> Optional[str]:
    """从指定网络接口获取ISP分配的IPv6前缀"""
    try:
        # 使用ip命令获取IPv6地址
        result = subprocess.run(
            ["ip", "-6", "addr", "show", interface, "scope", "global"],
            capture_output=True,
            text=True,
            check=True
        )
        
        for line in result.stdout.split('\n'):
            if "inet6" in line and not line.strip().startswith("inet6 fe80"):
                # 提取IPv6地址和前缀长度
                parts = line.strip().split()
                if len(parts) >= 2:
                    addr_with_prefix = parts[1]
                    # 分离地址和前缀长度
                    if '/' in addr_with_prefix:
                        full_addr = addr_with_prefix.split('/')[0]
                        # 提取前64位作为前缀(通常ISP分配/64前缀)
                        if ':' in full_addr:
                            # 获取前4个hextet(64位)
                            hextets = full_addr.split(':')
                            if len(hextets) >= 4:
                                prefix = ':'.join(hextets[:4]) + '::'
                                logger.info(f"获取到IPv6前缀: {prefix}")
                                return prefix
        logger.warning(f"在接口 {interface} 上未找到有效的全局IPv6地址")
        return None
        
    except subprocess.CalledProcessError as e:
        logger.error(f"执行ip命令失败: {e}")
        return None
    except Exception as e:
        logger.error(f"获取IPv6前缀时出错: {e}")
        return None

def construct_full_ipv6(prefix: str, suffix: str) -> str:
    """组合前缀和后缀生成完整的IPv6地址"""
    # 确保前缀以"::"结尾
    if not prefix.endswith('::'):
        prefix = prefix.rstrip(':') + '::'
    
    # 移除后缀可能存在的"::"
    suffix = suffix.lstrip(':')
    
    # 组合成完整地址
    full_ip = prefix + suffix
    logger.info(f"生成的完整IPv6地址: {full_ip}")
    return full_ip

def update_cloudflare_dns(full_ipv6: str, record_name: str, zone_id: str, record_id: str, api_token: str) -> bool:
    """更新CloudFlare的DNS记录"""
    url = f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records/{record_id}"
    
    headers = {
        "Authorization": f"Bearer {api_token}",
        "Content-Type": "application/json"
    }
    
    data = {
        "type": "AAAA",
        "name": record_name,
        "content": full_ipv6,
        "ttl": 120,  # 2分钟TTL,便于快速更新
        "proxied": False  # 不经过CloudFlare代理
    }
    
    try:
        response = requests.put(url, headers=headers, json=data, timeout=10)
        response.raise_for_status()
        
        result = response.json()
        if result.get("success"):
            logger.info(f"成功更新DNS记录 {record_name} -> {full_ipv6}")
            return True
        else:
            logger.error(f"CloudFlare API返回错误: {result.get('errors', [])}")
            return False
            
    except requests.exceptions.RequestException as e:
        logger.error(f"更新DNS记录失败: {e}")
        return False

def update_godaddy_dns(full_ipv6: str, record_name: str, api_key: str, api_secret: str) -> bool:
    """更新GoDaddy的DNS记录(示例)"""
    # GoDaddy API实现类似,这里省略具体代码
    # 实际使用时需要根据GoDaddy API文档实现
    pass

def main():
    """主循环:定期检查并更新DNS"""
    logger.info("启动IPv6动态DNS更新服务")
    
    last_ip = None
    
    while True:
        try:
            # 1. 获取当前IPv6前缀
            prefix = get_ipv6_prefix(INTERFACE)
            if not prefix:
                logger.warning("无法获取IPv6前缀,等待下次检查")
                time.sleep(CHECK_INTERVAL)
                continue
            
            # 2. 生成完整IPv6地址
            current_ip = construct_full_ipv6(prefix, CUSTOM_SUFFIX)
            
            # 3. 检查IP是否变化
            if current_ip != last_ip:
                logger.info(f"检测到IP变化: {last_ip} -> {current_ip}")
                
                # 4. 更新DNS记录
                if DNS_PROVIDER == "cloudflare":
                    success = update_cloudflare_dns(
                        current_ip, RECORD_NAME, ZONE_ID, 
                        RECORD_ID, API_TOKEN
                    )
                    if success:
                        last_ip = current_ip
                else:
                    logger.error(f"不支持的DNS服务商: {DNS_PROVIDER}")
            else:
                logger.debug("IP未变化,跳过更新")
            
        except Exception as e:
            logger.error(f"主循环出错: {e}")
        
        # 等待下次检查
        time.sleep(CHECK_INTERVAL)

if __name__ == "__main__":
    # 直接运行一次(适合cron定时任务)
    # 或运行main()进入循环模式
    
    # 单次执行模式(推荐用于cron):
    prefix = get_ipv6_prefix(INTERFACE)
    if prefix:
        full_ip = construct_full_ipv6(prefix, CUSTOM_SUFFIX)
        update_cloudflare_dns(full_ip, RECORD_NAME, ZONE_ID, RECORD_ID, API_TOKEN)
    
    # 循环执行模式:
    # main()

核心原理:

  1. 获取前缀:从网络接口获取ISP动态分配的IPv6前缀(通常是前64位)
  2. 组合地址:将ISP前缀与你自定义的后缀(如::1)组合成完整IPv6地址
  3. DNS更新:通过DNS服务商API更新AAAA记录

使用前需要:

  • 替换脚本中的配置参数(接口名、后缀、API密钥等)
  • 在DNS服务商处预先创建AAAA记录并获取Record ID
  • 安装依赖:pip install requests

运行方式:

  • 单次执行:直接运行脚本,适合cron定时任务(如每5分钟执行一次)
  • 常驻运行:取消注释main()调用,脚本会持续监控IP变化

一句话总结: 抓前缀、拼地址、调API,三步搞定IPv6动态解析。

这个超简单的啊,你都有 OpenWRT 了自己写个更新 DDNS 的脚本不就好了。
路由器上自己能拿到 prefix,然后在路由器上查询下面设备的 IP 的话,要么就关掉隐私扩展,这样后面会是固定的 EUI64 ;要么用 ip neigh -6 配合 MAC 地址查询。

关键是如何让内网的每台设备都有自己的公网 ipv6 地址?

这个现在应该大部分地区的家庭宽带都有了,ISP 都已经下发公网 IPV6 地址了,只需要配好 openwrt 路由,就能获取到。

这样岂不是相当于所有设备都有了公网地址?!那基本能直连家里的电脑了,如果不封端口,那就爽翻天了。

是的,我家目前就实现了这个。只是现在想把这些设备都有效地管理起来。IPV6 地址不好记,只能通过 DDNS 把 IPV6 地址解析到域名上去

不需要 ddns 啊,直接把域名的 AAAA 记录设置成你的 ipv6 地址就成了

ISP 给你提供的 ipv6 前缀每隔多少时间或者每次重启拨号会变化的。所以要 DDNS。

回到顶部