自动同步光猫IPv6信息至路由器静态IPv6配置

本文最后更新于 2025年10月2日 中午

自动同步光猫IPv6信息至路由器静态IPv6配置

一、背景

  • 目标:让内网服务器在移动宽带环境下 可以被外网访问
  • 问题来源:
    • 以前用电信宽带时可以把光猫桥接,路由器拨号获取公网 IPv4,配合 DDNS +路由器端口映射就能外网访问。
    • 现在使用的移动宽带不允许光猫桥接,且路由器即便拨号也没有公网 IPv4,只有公网 IPv6(GUA)。
    • 光猫分配给局端(路由器)的 IPv6 前缀会变(运营商或光猫重拨/更新),导致路由器IPv6在 native 模式下有时无法正确获取/更新 IPv6 前缀,影响外网可达性。
  • 因此想法:自动从光猫抓取当前的 IPv6 前缀 / 网关 / DNS 并自动把路由器设置为静态 IPv6,以保证路由器始终拥有正确的公网 IPv6 地址/前缀,从而 DDNS + IPv6 使内网服务器对外可达。

二、总体方案概述

  • 在内网服务器上写一个小服务(Python),定期(每 5 分钟)执行:
    1. 登录光猫 Web 管理页面,解析并提取当前的 IPv6 信息(前缀、网关、DNS)。
    2. 安全地注销光猫会话
    3. 判断该信息是否与最近一次成功配置的相同;相同则跳过。
    4. 若不同则登录路由器的管理 API,并调用设置接口更新路由器静态 IPv6配置(填写 WAN IP、LAN前缀、网关、DNS)。
    5. 安全地注销路由器会话
    6. 把当前结果写入状态文件并记录日志以便追踪。
  • 以 systemd 服务运行该脚本,开启开机自启并持续运行。

三、关键文件与运行方式

Python 脚本(主要工作逻辑)

  • 脚本路径:/home/username/ipv6-sync/ipv6-sync.py
  • 主要步骤:
    1. get_ipv6_info_from_modem():请求光猫登录页提取 Frm_SESSION_TOKEN,用令牌和密文登录,再抓取页面 status_wanstatu_ipv6wansta_t.gch,解析出 prefixgatewaydns1dns2,并主动注销光猫会话
    2. 判断与上次成功配置是否相同(is_same_as_last_success)。
    3. login_router():调用路由器的登录 API(luci 风格),取得 stok 和 session。
    4. set_router_static_ipv6():构造路由器需要的参数并 POST 到路由器的 /api/xqnetwork/set_wan6 接口。
    5. logout_router()主动注销路由器会话
    6. save_state():把执行结果和抓取到的信息写到状态文件。
  • 配置集中化:所有URL在脚本开头统一配置,风格规范,易于维护。
  • 状态与日志:使用相对路径(依赖于systemd服务设置的WorkingDirectory),记录执行状态与轮转日志。

具体实现

设备软硬件环境

  • 光猫:
    • 型号:SK-D840N
    • 软件版本:v2.0.0
    • 硬件版本:v2.0
  • 路由器:
    • 型号:红米AX6000
    • 系统版本:1.0.67
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
#!/usr/bin/env python3
import requests
from bs4 import BeautifulSoup
import time
import json
import os
import logging
from logging.handlers import RotatingFileHandler
from typing import Optional, Dict, Any, Tuple

# --- 配置信息 ---
MODEM_IP = "192.168.1.1"
MODEM_USERNAME = "user"
MODEM_LOGINCODE = "GKvrMrxxxxxxxyowRJCu6A=="

# 光猫相关URL
MODEM_BASE_URL = f"http://{MODEM_IP}"
MODEM_LOGIN_URL = f"{MODEM_BASE_URL}/"
MODEM_TEMPLATE_URL = f"{MODEM_BASE_URL}/template.gch"
MODEM_INFO_URL = f"{MODEM_BASE_URL}/getpage.gch?pid=1002&nextpage=status_wanstatu_ipv6wansta_t.gch"
MODEM_LOGOUT_URL = f"{MODEM_BASE_URL}/setlang.gch"

ROUTER_IP = "192.168.31.1"
ROUTER_USERNAME = "admin"
ROUTER_ENCRYPTED_PASSWORD = "a93423bed470cxxxxxxxxxxxxxx58d7a75077862462006db1942471d43ba83a19"
ROUTER_MAC = "d4:35:38:1c:ae:76"

# 路由器相关URL
ROUTER_BASE_URL = f"http://{ROUTER_IP}"
ROUTER_LOGIN_URL = f"{ROUTER_BASE_URL}/cgi-bin/luci/api/xqsystem/login"
ROUTER_SET_WAN6_URL = f"{ROUTER_BASE_URL}/cgi-bin/luci/;stok={{}}/api/xqnetwork/set_wan6"
ROUTER_LOGOUT_URL = f"{ROUTER_BASE_URL}/cgi-bin/luci/;stok={{}}/web/logout"

# 其他配置
ROUTER_LOGIN_PARAMS = "?username={}&password={}&logtype=2&nonce=0_58:11:22:4a:95:91_1759321406_9326"

# 状态文件路径
STATE_FILE = "state.json"

# 日志配置
LOG_FILE = "ipv6-sync.log"


class IPv6SyncService:
def __init__(self):
self.setup_logging()
self.last_state = self.load_state()
self.current_run_id = int(time.time())

def setup_logging(self):
"""配置日志系统"""
# 确保日志目录存在
log_dir = os.path.dirname(LOG_FILE)
if log_dir:
os.makedirs(log_dir, exist_ok=True)

# 创建logger
self.logger = logging.getLogger(__name__)
self.logger.setLevel(logging.INFO)

# 清除之前的handler,避免重复添加
if self.logger.handlers:
self.logger.handlers.clear()

# 设置日志格式
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')

# 文件handler - 限制最大5MB,保留2个备份文件
file_handler = logging.handlers.RotatingFileHandler(
LOG_FILE,
maxBytes=5 * 1024 * 1024, # 5MB
backupCount=2, # 保留2个备份文件
encoding='utf-8'
)
file_handler.setFormatter(formatter)

# 控制台handler
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)

# 添加handler到logger
self.logger.addHandler(file_handler)
self.logger.addHandler(console_handler)

def load_state(self) -> Dict[str, Any]:
"""加载上次执行的状态"""
try:
# 如果STATE_FILE包含路径(非纯文件名),则创建对应目录
state_dir = os.path.dirname(STATE_FILE)
if state_dir: # 只有当路径不为空时才创建目录
os.makedirs(state_dir, exist_ok=True)

if os.path.exists(STATE_FILE):
with open(STATE_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
self.logger.warning(f"加载状态文件失败: {e}")

return {
"last_ipv6_info": {},
"last_success_time": 0,
"last_run_result": "unknown"
}

def save_state(self, ipv6_info: Dict[str, Any], success: bool):
"""保存当前执行状态"""
try:
self.last_state = {
"last_ipv6_info": ipv6_info,
"last_success_time": self.current_run_id if success else self.last_state.get("last_success_time", 0),
"last_run_result": "success" if success else "failed",
"last_run_time": self.current_run_id
}
with open(STATE_FILE, 'w') as f:
json.dump(self.last_state, f, indent=2)
except Exception as e:
self.logger.error(f"保存状态文件失败: {e}")

def is_same_as_last_success(self, current_info: Dict[str, Any]) -> bool:
"""检查当前IPV6信息是否与上次成功的配置相同"""
last_info = self.last_state.get("last_ipv6_info", {})

if not last_info:
return False

# 比较关键字段
key_fields = ['prefix', 'gateway', 'dns1', 'dns2']
for field in key_fields:
if current_info.get(field) != last_info.get(field):
return False

return self.last_state.get("last_run_result") == "success"

def get_ipv6_info_from_modem(self) -> Optional[Dict[str, Any]]:
"""从光猫获取IPv6信息"""
session = requests.Session()
session.timeout = 30

try:
self.logger.info("正在访问光猫登录页面以获取动态令牌...")
login_page_response = session.get(MODEM_LOGIN_URL)
login_page_response.raise_for_status()

soup = BeautifulSoup(login_page_response.text, 'html.parser')
token_input = soup.find('input', {'name': 'Frm_SESSION_TOKEN'})
if not token_input:
self.logger.error("错误:无法在光猫登录页面找到 Frm_SESSION_TOKEN")
session.close() # 关闭会话
return None

token_value = token_input['value']
self.logger.debug(f"成功获取到 Frm_SESSION_TOKEN: {token_value}")

self.logger.info("正在使用令牌和密文登录光猫...")
login_data = {
"frashnum": "", "action": "login", "Frm_SESSION_TOKEN": token_value,
"username": MODEM_USERNAME, "logincode": MODEM_LOGINCODE,
"textpwd": "", "ieversion": "1"
}
login_response = session.post(MODEM_LOGIN_URL, data=login_data)
login_response.raise_for_status()

if login_response.url != MODEM_LOGIN_URL + 'start.ghtml':
self.logger.error("光猫登录失败!")
session.close() # 关闭会话
return None

self.logger.info("✅ 光猫登录成功!")

self.logger.info("正在获取会话令牌...")
template_response = session.get(MODEM_TEMPLATE_URL)
template_response.raise_for_status()

# 从JavaScript代码中提取session_token
session_token_user = None
import re
pattern = r'var\s+session_token\s*=\s*"([^"]+)"'
match = re.search(pattern, template_response.text)
if match:
session_token_user = match.group(1)
self.logger.debug(f"成功获取到 session_token: {session_token_user}")
else:
self.logger.warning("未能在template.gch中找到session_token")

self.logger.info("正在获取并解析光猫IPv6连接信息...")
info_response = session.get(MODEM_INFO_URL)
info_response.raise_for_status()

info_soup = BeautifulSoup(info_response.text, 'html.parser')
ipv6_info = {}

prefix_td = info_soup.find('td', string='获取前缀')
if prefix_td and prefix_td.find_next_sibling('td'):
ipv6_info['prefix'] = prefix_td.find_next_sibling('td').text.strip()
else:
self.logger.warning("未找到IPv6前缀信息")
session.close() # 关闭会话
return None

# 提取其他信息
ipv6_info['dns1'] = info_soup.find('input', {'id': 'Sta_PPP_DNS1_1'})['value'] if info_soup.find('input', {
'id': 'Sta_PPP_DNS1_1'}) else ''
ipv6_info['dns2'] = info_soup.find('input', {'id': 'Sta_PPP_DNS2_1'})['value'] if info_soup.find('input', {
'id': 'Sta_PPP_DNS2_1'}) else ''
ipv6_info['gateway'] = info_soup.find('input', {'id': 'Sta_PPP_GateW_1'})['value'] if info_soup.find(
'input', {'id': 'Sta_PPP_GateW_1'}) else ''

self.logger.info("✅ 成功从光猫获取到IPv6信息")
self.logger.info(f" 前缀: {ipv6_info.get('prefix')}")
self.logger.info(f" 网关: {ipv6_info.get('gateway')}")
self.logger.info(f" DNS1: {ipv6_info.get('dns1')}")
self.logger.info(f" DNS2: {ipv6_info.get('dns2')}")

# === 注销光猫会话 ===
self.logger.info("正在注销光猫会话...")
try:
if session_token_user:
logout_url = f"{MODEM_LOGOUT_URL}?logout=1&_SESSION_TOKEN_USER={session_token_user}"
logout_response = session.post(logout_url, timeout=5, allow_redirects=False)

if logout_response.status_code == 302:
self.logger.info("✅ 光猫会话注销成功 (302重定向)")
elif logout_response.status_code == 200:
# 检查是否返回登录页面
if "login" in logout_response.text.lower() or "password" in logout_response.text.lower():
self.logger.info("✅ 光猫会话注销成功 (返回登录页面)")
else:
self.logger.warning("⚠️ 注销返回200,但可能仍在登录状态")
else:
self.logger.warning(f"⚠️ 注销请求返回状态码: {logout_response.status_code}")
else:
self.logger.warning("⚠️ 无法获取session_token_user,跳过注销")

except Exception as e:
self.logger.warning(f"注销光猫会话时发生错误: {e}")
finally:
# 最终确保会话被关闭
session.close()
self.logger.info("✅ 光猫会话已关闭")

return ipv6_info

except requests.exceptions.RequestException as e:
self.logger.error(f"光猫网络请求失败: {e}")
session.close() # 发生异常时也要关闭会话
return None
except Exception as e:
self.logger.error(f"光猫操作发生未知错误: {e}")
session.close() # 发生异常时也要关闭会话
return None

def login_router(self) -> Optional[Tuple[requests.Session, str]]:
"""登录路由器"""
session = requests.Session()
session.timeout = 30

try:
self.logger.info("正在登录路由器...")
login_url_with_params = ROUTER_LOGIN_URL + ROUTER_LOGIN_PARAMS.format(ROUTER_USERNAME,
ROUTER_ENCRYPTED_PASSWORD)
headers = {
"User-Agent": "Mozilla/5.0 (compatible; IPv6-Sync-Service/1.0)",
"Connection": "keep-alive",
"Accept": "*/*"
}

login_response = session.get(login_url_with_params, headers=headers)
login_response.raise_for_status()

result = login_response.json()
if result.get("code") == 0:
router_stok = result.get("token")
self.logger.info(f"✅ 路由器登录成功,获取到新的stok: {router_stok}")
return session, router_stok
else:
self.logger.error(f"❌ 路由器登录失败,错误信息: {result.get('msg')}")
session.close() # 登录失败时关闭会话
return None

except requests.exceptions.RequestException as e:
self.logger.error(f"路由器登录网络请求失败: {e}")
session.close() # 发生异常时关闭会话
return None
except Exception as e:
self.logger.error(f"路由器登录发生未知错误: {e}")
session.close() # 发生异常时关闭会话
return None

def logout_router(self, router_session: requests.Session, router_stok: str) -> bool:
"""注销路由器会话"""
try:
self.logger.info("正在注销路由器会话...")
logout_url = ROUTER_LOGOUT_URL.format(router_stok)

logout_response = router_session.post(logout_url, timeout=5, allow_redirects=False)

if logout_response.status_code == 302:
self.logger.info("✅ 路由器会话注销成功 (302重定向)")
return True
else:
self.logger.warning(f"⚠️ 路由器注销返回状态码: {logout_response.status_code}")
return False

except Exception as e:
self.logger.warning(f"注销路由器会话时发生错误: {e}")
return False

def set_router_static_ipv6(self, router_session: requests.Session, router_stok: str,
ipv6_info: Dict[str, Any]) -> bool:
"""设置路由器的静态IPv6"""
if not ipv6_info or not router_session:
self.logger.error("缺少有效信息或未成功登录路由器,无法设置")
return False

try:
router_api_url = ROUTER_SET_WAN6_URL.format(router_stok)

# 构造IPv6地址
pure_prefix, assign = ipv6_info['prefix'].split('/')
clean_mac = ROUTER_MAC.replace(':', '')
ipaddr = f"{pure_prefix[:-1]}ffff:{clean_mac[:4]}:{clean_mac[4:8]}:{clean_mac[8:]}"

# 构造请求参数
params = {
"wanType": "static",
"ipaddr": ipaddr,
"gw": ipv6_info.get('gateway', ''),
"prefix": pure_prefix,
"assign": assign,
"dns1": ipv6_info.get('dns1', ''),
"dns2": ipv6_info.get('dns2', '')
}

params_str = "&".join([f"{key}={value}" for key, value in params.items()])
self.logger.info("路由器配置参数详情:")
self.logger.info(f" WAN口IP: {params['ipaddr']}")
self.logger.info(f" 网关: {params['gw']}")
self.logger.info(f" 前缀: {params['prefix']}")
self.logger.info(f" 前缀长度: {params['assign']}")
self.logger.info(f" DNS1: {params['dns1']}")
self.logger.info(f" DNS2: {params['dns2']}")

self.logger.info("正在向路由器发送设置请求...")
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}

response = router_session.post(router_api_url, data=params_str, headers=headers)
response.raise_for_status()

result = response.json()
success = result.get("code") == 0

if success:
self.logger.info("✅ 路由器静态IPv6设置成功!")
else:
self.logger.error(f"❌ 路由器设置失败!返回内容: {result}")

return success

except requests.exceptions.RequestException as e:
self.logger.error(f"路由器设置网络请求失败: {e}")
return False
except KeyError as e:
self.logger.error(f"从光猫信息中缺少必要的字段: {e}")
return False
except Exception as e:
self.logger.error(f"路由器设置操作发生未知错误: {e}")
return False

def run(self):
"""主执行逻辑"""
self.logger.info(f"=== IPv6同步服务开始执行 (Run ID: {self.current_run_id}) ===")

# 步骤1: 采集信息 - 从光猫获取IPv6配置
self.logger.info("【步骤1/4】信息采集 - 正在从光猫获取IPv6信息...")
modem_ipv6_data = self.get_ipv6_info_from_modem()
if not modem_ipv6_data:
self.logger.error("❌ 信息采集失败,无法获取光猫IPv6信息,本次执行终止")
self.save_state({}, False)
return
self.logger.info("✅ 信息采集成功")

# 步骤2: 决策判断 - 检查信息是否变化
self.logger.info("【步骤2/4】决策判断 - 检查IPv6信息是否发生变化...")
if self.is_same_as_last_success(modem_ipv6_data):
self.logger.info("✅ 决策结果:信息未变化,无需更新路由器配置")
self.save_state(modem_ipv6_data, True)
self.logger.info("=== IPv6同步服务执行完成 (结果: ✅ 跳过) ===")
return
else:
self.logger.info("🔄 决策结果:信息已变化,需要更新路由器配置")

# 步骤3: 执行配置 - 登录、配置、注销路由器
self.logger.info("【步骤3/4】执行配置 - 开始配置路由器...")
router_login_result = self.login_router()
if not router_login_result:
self.logger.error("❌ 执行配置失败:路由器登录无效")
self.save_state(modem_ipv6_data, False)
return

router_session, router_stok = router_login_result
success = False

try:
# 核心配置操作
self.logger.info(" 正在向路由器应用新的IPv6静态配置...")
success = self.set_router_static_ipv6(router_session, router_stok, modem_ipv6_data)

# 配置后清理
self.logger.info(" 正在注销路由器会话...")
self.logout_router(router_session, router_stok)

except Exception as e:
self.logger.error(f"❌ 执行配置过程中发生错误: {e}")
finally:
# 确保资源释放
router_session.close()
self.logger.info("✅ 路由器会话连接已关闭")

if success:
self.logger.info("✅ 路由器配置执行成功")
else:
self.logger.error("❌ 路由器配置执行失败")

# 步骤4: 保存状态
self.logger.info("【步骤4/4】持久化 - 正在保存本次执行状态...")
self.save_state(modem_ipv6_data, success)
self.logger.info(f"=== IPv6同步服务执行完成 (结果: {'✅ 成功' if success else '❌ 失败'}) ===\n")


def main():
"""主函数 - 用于单次执行"""
service = IPv6SyncService()
service.run()


def run_as_service():
"""服务模式 - 每5分钟执行一次"""
service = IPv6SyncService()
service.logger.info("IPv6同步服务已启动,将每5分钟执行一次")

while True:
try:
service.current_run_id = int(time.time())
service.run()
except Exception as e:
service.logger.error(f"服务执行过程中发生未捕获的异常: {e}")

# 等待5分钟
time.sleep(300)


if __name__ == "__main__":
# 根据参数决定运行模式
import sys

if len(sys.argv) > 1 and sys.argv[1] == "--service":
run_as_service()
else:
main()


systemd 服务文件

  • 服务文件路径:/etc/systemd/system/ipv6-sync.service
  • 关键配置:使用 WorkingDirectory 指定工作目录,使脚本内的文件路径(状态文件、日志)可使用相对路径,提升可维护性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Unit]
Description=IPv6 Sync Service
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/home/username/ipv6-sync
ExecStart=/home/username/miniconda3/bin/python ipv6-sync.py --service
Restart=always
RestartSec=60
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

部署与管理命令

1
2
3
4
5
6
7
8
9
10
# 拷贝服务文件后,重载systemd配置
sudo systemctl daemon-reload
# 启用服务(开机自启)
sudo systemctl enable ipv6-sync.service
# 启动服务
sudo systemctl start ipv6-sync.service
# 查看服务状态
sudo systemctl status ipv6-sync.service
# 查看服务日志
sudo journalctl -u ipv6-sync.service -f

运行日志示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
2025-10-02 11:57:02,171 - INFO - IPv6同步服务已启动,将每5分钟执行一次
2025-10-02 11:57:02,171 - INFO - === IPv6同步服务开始执行 (Run ID: 1759377422) ===
2025-10-02 11:57:02,171 - INFO - 【步骤1/4】信息采集 - 正在从光猫获取IPv6信息...
2025-10-02 11:57:02,172 - INFO - 正在访问光猫登录页面以获取动态令牌...
2025-10-02 11:57:02,206 - INFO - 正在使用令牌和密文登录光猫...
2025-10-02 11:57:02,312 - INFO - ✅ 光猫登录成功!
2025-10-02 11:57:02,313 - INFO - 正在获取会话令牌...
2025-10-02 11:57:02,401 - INFO - 正在获取并解析光猫IPv6连接信息...
2025-10-02 11:57:02,485 - INFO - ✅ 成功从光猫获取到IPv6信息
2025-10-02 11:57:02,485 - INFO - 前缀: 2409:8a28:4a14:6dd0::/60
2025-10-02 11:57:02,485 - INFO - 网关: fe80::ea4:2ff:feb1:3401
2025-10-02 11:57:02,485 - INFO - DNS1: 2409:8028:2000::1111
2025-10-02 11:57:02,485 - INFO - DNS2: 2409:8028:2000::2222
2025-10-02 11:57:02,485 - INFO - 正在注销光猫会话...
2025-10-02 11:57:02,509 - INFO - ✅ 光猫会话注销成功 (302重定向)
2025-10-02 11:57:02,510 - INFO - ✅ 光猫会话已关闭
2025-10-02 11:57:02,510 - INFO - ✅ 信息采集成功
2025-10-02 11:57:02,510 - INFO - 【步骤2/4】决策判断 - 检查IPv6信息是否发生变化...
2025-10-02 11:57:02,511 - INFO - ✅ 决策结果:信息未变化,无需更新路由器配置
2025-10-02 11:57:02,512 - INFO - === IPv6同步服务执行完成 (结果: ✅ 跳过) ===
2025-10-02 11:57:45,493 - INFO - IPv6同步服务已启动,将每5分钟执行一次
2025-10-02 11:57:45,494 - INFO - === IPv6同步服务开始执行 (Run ID: 1759377465) ===
2025-10-02 11:57:45,494 - INFO - 【步骤1/4】信息采集 - 正在从光猫获取IPv6信息...
2025-10-02 11:57:45,494 - INFO - 正在访问光猫登录页面以获取动态令牌...
2025-10-02 11:57:45,532 - INFO - 正在使用令牌和密文登录光猫...
2025-10-02 11:57:45,633 - INFO - ✅ 光猫登录成功!
2025-10-02 11:57:45,634 - INFO - 正在获取会话令牌...
2025-10-02 11:57:45,722 - INFO - 正在获取并解析光猫IPv6连接信息...
2025-10-02 11:57:45,806 - INFO - ✅ 成功从光猫获取到IPv6信息
2025-10-02 11:57:45,807 - INFO - 前缀: 2409:8a28:4a14:6dd0::/60
2025-10-02 11:57:45,807 - INFO - 网关: fe80::ea4:2ff:feb1:3401
2025-10-02 11:57:45,807 - INFO - DNS1: 2409:8028:2000::1111
2025-10-02 11:57:45,807 - INFO - DNS2: 2409:8028:2000::2222
2025-10-02 11:57:45,807 - INFO - 正在注销光猫会话...
2025-10-02 11:57:45,832 - INFO - ✅ 光猫会话注销成功 (302重定向)
2025-10-02 11:57:45,832 - INFO - ✅ 光猫会话已关闭
2025-10-02 11:57:45,833 - INFO - ✅ 信息采集成功
2025-10-02 11:57:45,833 - INFO - 【步骤2/4】决策判断 - 检查IPv6信息是否发生变化...
2025-10-02 11:57:45,833 - INFO - 🔄 决策结果:信息已变化,需要更新路由器配置
2025-10-02 11:57:45,833 - INFO - 【步骤3/4】执行配置 - 开始配置路由器...
2025-10-02 11:57:45,834 - INFO - 正在登录路由器...
2025-10-02 11:57:45,923 - INFO - ✅ 路由器登录成功,获取到新的stok: 8fa59a2c364ed5cc12cd5d19b6477f0d
2025-10-02 11:57:45,924 - INFO - 正在向路由器应用新的IPv6静态配置...
2025-10-02 11:57:45,924 - INFO - 路由器配置参数详情:
2025-10-02 11:57:45,924 - INFO - WAN口IP: 2409:8a28:4a14:6dd0:ffff:d435:381c:ae76
2025-10-02 11:57:45,924 - INFO - 网关: fe80::ea4:2ff:feb1:3401
2025-10-02 11:57:45,924 - INFO - 前缀: 2409:8a28:4a14:6dd0::
2025-10-02 11:57:45,925 - INFO - 前缀长度: 60
2025-10-02 11:57:45,925 - INFO - DNS1: 2409:8028:2000::1111
2025-10-02 11:57:45,925 - INFO - DNS2: 2409:8028:2000::2222
2025-10-02 11:57:45,925 - INFO - 正在向路由器发送设置请求...
2025-10-02 11:57:46,005 - INFO - ✅ 路由器静态IPv6设置成功!
2025-10-02 11:57:46,005 - INFO - 正在注销路由器会话...
2025-10-02 11:57:46,005 - INFO - 正在注销路由器会话...
2025-10-02 11:57:46,070 - INFO - ✅ 路由器会话注销成功 (302重定向)
2025-10-02 11:57:46,071 - INFO - ✅ 路由器会话连接已关闭
2025-10-02 11:57:46,071 - INFO - ✅ 路由器配置执行成功
2025-10-02 11:57:46,072 - INFO - 【步骤4/4】持久化 - 正在保存本次执行状态...
2025-10-02 11:57:46,072 - INFO - === IPv6同步服务执行完成 (结果: ✅ 成功) ===

2025-10-02 12:02:46,073 - INFO - === IPv6同步服务开始执行 (Run ID: 1759377766) ===
2025-10-02 12:02:46,073 - INFO - 【步骤1/4】信息采集 - 正在从光猫获取IPv6信息...
2025-10-02 12:02:46,074 - INFO - 正在访问光猫登录页面以获取动态令牌...
2025-10-02 12:02:46,114 - INFO - 正在使用令牌和密文登录光猫...
2025-10-02 12:02:46,221 - INFO - ✅ 光猫登录成功!
2025-10-02 12:02:46,221 - INFO - 正在获取会话令牌...
2025-10-02 12:02:46,305 - INFO - 正在获取并解析光猫IPv6连接信息...
2025-10-02 12:02:46,389 - INFO - ✅ 成功从光猫获取到IPv6信息
2025-10-02 12:02:46,390 - INFO - 前缀: 2409:8a28:4a14:6dd0::/60
2025-10-02 12:02:46,390 - INFO - 网关: fe80::ea4:2ff:feb1:3401
2025-10-02 12:02:46,390 - INFO - DNS1: 2409:8028:2000::1111
2025-10-02 12:02:46,390 - INFO - DNS2: 2409:8028:2000::2222
2025-10-02 12:02:46,390 - INFO - 正在注销光猫会话...
2025-10-02 12:02:46,416 - INFO - ✅ 光猫会话注销成功 (302重定向)
2025-10-02 12:02:46,416 - INFO - ✅ 光猫会话已关闭
2025-10-02 12:02:46,417 - INFO - ✅ 信息采集成功
2025-10-02 12:02:46,417 - INFO - 【步骤2/4】决策判断 - 检查IPv6信息是否发生变化...
2025-10-02 12:02:46,417 - INFO - ✅ 决策结果:信息未变化,无需更新路由器配置
2025-10-02 12:02:46,418 - INFO - === IPv6同步服务执行完成 (结果: ✅ 跳过) ===

四、DDNS 配置建议

  • 推荐使用 Docker 部署 DDNS-GO
  • IP获取方式:选择 “通过网卡获取”
  • IPv6正则过滤:使用正则表达式筛选出纯公网IPv6地址,避免使用临时地址或链路本地地址。
    1
    ^24[0-9a-fA-F:]+$
    (此正则示例匹配以24开头的GUA地址,可根据实际情况调整)

五、注意事项

  • 接口获取方法是通过开发者模式网络请求查看负载,要注意在网络选项卡中勾选保留日志,否则网络请求过多时前面的请求会被覆盖
  • 通过查看负载发现某些字段未知时,通过字段名在网络选项卡中搜索,查看生成逻辑并抓取
  • 建议抓取光猫和路由器的注销接口,每次请求完主动注销,并关闭session连接,避免光猫路由器缓存临时会话过多导致崩溃(实测5分钟一次请求,不主动注销的情况下,请求几百次光猫就崩溃了)

自动同步光猫IPv6信息至路由器静态IPv6配置
https://xinhaojin.github.io/2025/10/01/自动同步光猫IPv6信息至路由器静态IPv6配置/
作者
xinhaojin
发布于
2025年10月1日
许可协议