现在大部分地区宽带和手机热点都支持ipv6,相比起公网ipv4地址,个人获取公网ipv6地址还是挺容易的,不过大部分情况都难以获取到固定的ipv6地址,需要利用DDNS事实更新。本文主要介绍如何利用dynv6的http接口进行ipv6的实施更新。
1. 准备
1.1 创建dynv6账号和域名
网上教程很多,不赘述。
1.2 创建dynv6 token
需要创建token作为更新ipv6地址的凭据
然后点击+Add Http Token
,随便起个名字或者不写,Zone
选择All
(可以更新所有创建的域名)或者选择自己要更新的那个域名。
1.3 确认已开启IPv6
windows的cmd(或power shell)中执行ipconfig,查看ipv6地址,如果没有IPv6地址,则说明相关功能未开启,先确认网卡IPv6是否开启,再确认上级路由/入户光猫是否开启了相关功能,可以和运行商沟通。
2. 脚本内容
脚本整体思路是
-
解析域名地址对应的IPv6
-
获取本地IPv6,通过cmd运行ipconfig返回内容中的
IPv6 地址...
来解析获取 -
若本地IPv6和解析域名得到的IPv6不一致,则更新IP地址
为了便于移植,把token/域名等写入配置文件,直接修改配置文件就可以用到其他地方了。
import urllib.request
import subprocess
import re
import sys
import time
import json
import socket
import threading
import datetime
def getIpv6Addr(interface_name):
output = subprocess.check_output(["ipconfig", ],
stderr=subprocess.STDOUT)
output = output.decode("gbk", errors="ignore")
#print("output type is "+str(type(output)))
outlines = output.split('\n')
# 找到对应网络适配器的起始位置
for idx in range(0, len(outlines)):
if (-1 != outlines[idx].find(interface_name)):
break
#print("start line is "+str(idx)+", content:")
#print(outlines[idx])
# 继续向下找ipv6地址
while (idx < len(outlines)):
if (-1 != outlines[idx].find("IPv6 地址") and
-1 == outlines[idx].find("临时") and
-1 == outlines[idx].find("本地链接")):
#print("get ipv6 addr: "+outlines[idx])
start = outlines[idx].find(": ") + 2
end = len(outlines[idx]) - 1
return outlines[idx][start:end], ""
elif(-1 != outlines[idx].find("默认网关")):
break
idx += 1
return None, "Error: no ipv6 address."
def getValueByKeyFromJsonFile(key):
try:
with open("F:\\share\\SourceCode\\AutoUpdateIPV6\\Dynv6Config.json", "r") as f:
data = json.load(f)
if(not key in data):
return "", "No such key: " + key
return data[key], ""
except Exception as e:
return None, "系统错误: " + str(e)
def resolveDomain2Ipv6(domain):
"""
解析域名的IPv6地址(AAAA记录)
参数:
domain: 要解析的域名(如"example.com" )
返回:
tuple: (有效IPv6地址列表, 错误信息)
"""
try:
subprocess.run(['ipconfig', '/flushdns'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True)
#print("DNS 缓存清理成功!")
except Exception as e:
#print(f"清理 DNS 缓存失败: {e}")
return (None, "系统错误: " + str(e))
try:
# 设置地址族为IPv6,类型为流式TCP
results = socket.getaddrinfo(
domain,
None,
family=socket.AF_INET6,
type=socket.SOCK_STREAM
)
# 提取唯一地址并标准化格式
ipv6_addresses = list({addr[4][0] for addr in results})
# 按RFC5952标准排序地址
ipv6_addresses.sort(key=lambda x: socket.inet_pton(socket.AF_INET6, x))
return (ipv6_addresses, "")
except socket.gaierror as e:
error_map = {
socket.EAI_NONAME: "域名不存在",
socket.EAI_AGAIN: "DNS查询超时",
socket.EAI_FAIL: "DNS服务器拒绝查询"
}
return (None, "解析失败: " + error_map.get(e.errno, '未知错误'))
except Exception as e:
return (None, "系统错误: " + str(e))
if __name__ == "__main__":
dynv6_server = "dynv6.com"
dynv6_port = 80
httpRequestHeader = ""
interface, errStr = getValueByKeyFromJsonFile("interface")
if (0 != len(errStr)):
print(str(datetime.datetime.now()) + "err: " + errStr)
hostname , errStr = getValueByKeyFromJsonFile("hostname")
if (0 != len(errStr)):
print(str(datetime.datetime.now()) + "err: " + errStr)
token , errStr = getValueByKeyFromJsonFile("token")
if (0 != len(errStr)):
print(str(datetime.datetime.now()) + "err: " + errStr)
print("\n" + str(datetime.datetime.now()) + ": " + "network interface: " + interface)
print(str(datetime.datetime.now()) + ": " + "update hostname: " + hostname)
print(str(datetime.datetime.now()) + ": " + "dynv6 token: " + token + "\n")
while (True):
time.sleep(0.5)
# 获取当前域名对应的ipv6地址和本机ipv6地址
nowIpv6, errStr = resolveDomain2Ipv6(hostname)
if (0 != len(errStr)):
print(str(datetime.datetime.now()) + "resolve ipv6 failed: " + errStr)
continue
localIp, errStr = getIpv6Addr(interface)
if(0 != len(errStr)):
print(str(datetime.datetime.now()) + "get local ipv6 failed: " + errStr)
continue
# ip没变时,不更新ip
if(localIp in nowIpv6):
#print("ip not update, continue")
continue
print(str(datetime.datetime.now()) + ": " + "ip change, " + localIp +" is not in " + str(nowIpv6) + ", update it!")
# 构建http请求
requestMethod = "GET"
requestAPI = "/api/update?hostname=" + hostname + "&ipv6=" + localIp + "&token=" + token
requestHttpVer = "HTTP/1.1"
requestOther = "User-Agent: PostmanRuntime/7.43.0\r\n" + "Accept: */*\r\n" + "Host: dynv6.com\r\n" + \
"Accept-Encoding: gzip, deflate, br\r\n" + "Connection: close\r\n\r\n"
requestContent = requestMethod + " " + requestAPI + " " + requestHttpVer + "\r\n" + requestOther
print("request: \n" + requestContent)
# 建立tcp连接
try:
dynv6_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
dynv6_sock.settimeout(3.0)
dynv6_sock.connect((dynv6_server, dynv6_port))
except Exception as e:
print("Error: " + str(e))
dynv6_sock.close()
continue
# 发送数据
try:
dynv6_sock.sendall(requestContent.encode("utf-8"))
except Exception as e:
print("Error: " + str(e))
dynv6_sock.close()
continue
# 接受返回(也不是很必要,只是更新失败的时候可以从这里排查一下)
try:
responseContent = dynv6_sock.recv(1024)
print(str(datetime.datetime.now()) + ": " + "dynv6 server response:\n" + str(responseContent, encoding = "utf-8"))
except Exception as e:
print("Error: " + str(e))
dynv6_sock.close()
continue
dynv6_sock.close()
time.sleep(300) # 发送过一次请求至少60s后再发,避免频繁连接被拉黑
token配置文件格式参考如下
{
"hostname" : "luoxian****.v6.rocks",
"token" : "yU7VN8yeY6ceLS-pVbyy**********",
"interface" : "WLAN"
}
脚本执行后会刷类似下面的log
F:\share\SourceCode\AutoUpdateIPV6>python autoUpdateIpv6_v2.py
2025-06-15 01:56:18.400754: network interface: WLAN
2025-06-15 01:56:18.401254: update hostname: luoxian****.v6.rocks
2025-06-15 01:56:18.401254: dynv6 token: yU7VN8yeY6ceLS-pVbyyx*******
2025-06-15 01:56:19.010917resolve ipv6 failed: 解析失败: 域名不存在
2025-06-15 01:56:19.588773: ip change, 2408:8956:600:***** is not in ['2408:8956:7c40:*****'], update it!
request:
GET /api/update?hostname=luoxian****.v6.rocks&ipv6=2408:8956:600:*****&token=yU7VN8yeY6ceLS-pVbyyx******* HTTP/1.1
User-Agent: PostmanRuntime/7.43.0
Accept: */*
Host: dynv6.com
Accept-Encoding: gzip, deflate, br
Connection: close
2025-06-15 01:56:20.837319: dynv6 server response:
HTTP/1.1 200 OK
Content-Length: 17
Content-Type: text/plain; charset=UTF-8
Date: Sat, 14 Jun 2025 17:56:17 GMT
Vary: Origin
Via: 1.1 Caddy
Connection: close
addresses updated