From 2de45dabc78d9197be9fb39abe112aceba8dd5f8 Mon Sep 17 00:00:00 2001 From: Su Yijun Date: Sun, 25 Oct 2026 17:37:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8F=91=E5=B8=83=200.6.22=EF=BC=8C?= =?UTF-8?q?=E6=8E=A5=E5=85=A5=E5=90=91=E5=AF=BC=E4=B8=8E=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E8=A1=A5=E4=B8=8A=20IPv6=20=E5=97=85=E6=8E=A2=E5=92=8C=20FastT?= =?UTF-8?q?rack=20=E5=91=BD=E4=BB=A4=EF=BC=9B=E8=87=AA=E6=A3=80=E6=A3=80?= =?UTF-8?q?=E6=9F=A5=20IPv6=20FastTrack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 8 ++++++++ DOCKERHUB.md | 1 + README.md | 10 ++++++++++ backend/app/__init__.py | 2 +- backend/app/ros_poller.py | 8 +++++++- backend/tests/test_ros_poller.py | 8 ++++++++ docs/RouterOS配置.md | 22 +++++++++++++++++++++- frontend/src/i18n/en.js | 1 + frontend/src/i18n/zh.js | 1 + frontend/src/views/Settings.vue | 18 ++++++++++++++---- 10 files changed, 72 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb30ad6..9adf3e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [0.6.22] - 2026-10-25 + +接入向导、文档补上 IPv6 嗅探与 FastTrack;自检检查 IPv6 的 FastTrack + +- **接入向导**(系统设置 → 接入与自检)新增 3b、3c 两步:路由器开了 IPv6 时在 `/ipv6 firewall mangle` 再加一份 DNS / SNI 嗅探(4 条);开了 FastTrack 时让 IPv4 / IPv6 每条连接的前 10KB 不走 FastTrack。此前向导只有 IPv4 命令,IPv6 流量的 DNS 应答和握手全都镜像不到。 +- **接入自检**读取 `/ipv6/firewall/filter`:IPv6 的 FastTrack 规则(ROS 7.18 起支持)没有限定 connection-bytes 时提示,并给出修改命令。 +- README 与 `docs/RouterOS配置.md` 补充 IPv6 一节:为什么必须做、4 条命令、镜像目标可以用 ROS-DPI 的 IPv4 地址、流量采集不用另配。 + ## [0.6.21] - 2026-10-25 社区共享库重做:项目自带社区主站,不再依赖 WebDAV / 第三方存储(debug13~22 复查) diff --git a/DOCKERHUB.md b/DOCKERHUB.md index 488916a..5abbeb8 100644 --- a/DOCKERHUB.md +++ b/DOCKERHUB.md @@ -36,6 +36,7 @@ services: ## What's new +- **0.6.22**: 接入向导与文档补上 IPv6 嗅探(`/ipv6 firewall mangle` 4 条)和 FastTrack(IPv4 / IPv6)命令;自检检查 IPv6 的 FastTrack。Setup guide and docs now include IPv6 sniff rules and FastTrack commands for both families; the self-check also flags IPv6 FastTrack without a connection-bytes limit. - **0.6.21**: 社区共享库重做——项目自带社区主站,不再需要 WebDAV / 第三方存储:令牌注册、按来源网络计票(≥2 个不同网络一致才发布,防伪造实例刷票)、ETag 增量拉取、本地缓存;修复 0.6.20 部署量接口 500、共享任务从未运行、开关保存不上。接入自检新增 IPv6 嗅探规则检查,FastTrack 已限定 connection-bytes 时不再误报,并直接给出修改命令。Community library rebuilt around a built-in hub (no WebDAV/third-party storage): per-instance tokens, votes counted per source network, ETag pulls, local cache; fixes the 0.6.20 deployments 500 and the never-running share job. Self-check now flags missing IPv6 sniff rules. - **0.6.12 / 0.6.11 / 0.6.10 / 0.6.9 / 0.6.8**: 自学习与共享系列。**本地自学习库**:本网络实测的「IP → 应用」(IPv6 按 /64 前缀聚合)与「域名 → 应用」长期记忆,每小时重建,DNS 缓存过期、DoH、裸 IP 流量都能按记忆归类;**社区共享库**:各部署实例把实测结论匿名共享(中转聚合、至少 2 个实例见过才发布),本地实测优先、社区只补空白;**全球部署量**:实例心跳上报,中转公开已部署 / 在线 / 离线数量;共享总开关默认打开;**一键自更新**:Linux 二进制部署在网页上检测并一键升级(自动下载、替换、重启服务);**Linux 单文件二进制部署**:`install.sh` 一键安装 systemd 服务(SQLite 零配置起步);修复落库卡死(代理长域名撑爆 access_log.remote 列导致每分钟落库失败、在线终端归零);组播 / 广播流量不再计入统计;17 个 DPI 大类配色互异。 - **0.6.7**: fixes for the 0.6.5 / 0.6.6 history reclassification and rule auto-registration. Reclassification now only touches days before today and only upgrades vague rows (HTTPS / Chinese websites / unknown / CDN) to a specific app — it no longer downgrades apps identified live by IP range, port or ASN. New upstream blackmatrix7 directories are downloaded first and then assigned by a vote of the existing libraries. The manual dark theme now uses the 17 distinct group colors, and the AdGuard Home password is masked in the operation log. diff --git a/README.md b/README.md index b4c8c20..3a12dec 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,16 @@ EOF /ip firewall mangle add chain=forward protocol=tcp dst-port=443 connection-bytes=0-4000 action=sniff-tzsp sniff-target=192.168.88.10 sniff-target-port=37008 comment=ROSDPI-SNI /ip firewall mangle add chain=forward protocol=udp dst-port=443 connection-bytes=0-4000 action=sniff-tzsp sniff-target=192.168.88.10 sniff-target-port=37008 comment=ROSDPI-SNI +# 3b. 路由器开了 IPv6 时必须再加一份(国内宽带默认有 IPv6,大流量应用优先走 IPv6) +/ipv6 firewall mangle add chain=output protocol=udp src-port=53 action=sniff-tzsp sniff-target=192.168.88.10 sniff-target-port=37008 comment=ROSDPI-DNS +/ipv6 firewall mangle add chain=forward protocol=udp src-port=53 action=sniff-tzsp sniff-target=192.168.88.10 sniff-target-port=37008 comment=ROSDPI-DNS +/ipv6 firewall mangle add chain=forward protocol=tcp dst-port=443 connection-bytes=0-4000 action=sniff-tzsp sniff-target=192.168.88.10 sniff-target-port=37008 comment=ROSDPI-SNI +/ipv6 firewall mangle add chain=forward protocol=udp dst-port=443 connection-bytes=0-4000 action=sniff-tzsp sniff-target=192.168.88.10 sniff-target-port=37008 comment=ROSDPI-SNI + +# 3c. 开了 FastTrack 时:每条连接的前 10KB 不走 FastTrack,握手才镜像得到 +/ip firewall filter set [find action=fasttrack-connection] connection-bytes=10000-0 +/ipv6 firewall filter set [find action=fasttrack-connection] connection-bytes=10000-0 + # 4. 只读 API 账号(可选:主机名、WAN 速率) /user group add name=rosdpi policy=read,rest-api,api,!write,!policy,!sensitive /user add name=rosdpi group=rosdpi password=<强密码> address=192.168.88.10 diff --git a/backend/app/__init__.py b/backend/app/__init__.py index 0a932f9..588a08d 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -1 +1 @@ -__version__ = "0.6.21" +__version__ = "0.6.22" diff --git a/backend/app/ros_poller.py b/backend/app/ros_poller.py index 538bbe3..d130f06 100644 --- a/backend/app/ros_poller.py +++ b/backend/app/ros_poller.py @@ -43,6 +43,7 @@ class RosPoller: self.mangle: list[dict] = [] # 接入自检用:RouterOS 的 mangle 规则(只读) self.ip_settings: dict = {} self.filter_rules: list[dict] | None = None # /ip/firewall/filter(看 FastTrack 规则有没有限定 connection-bytes) + self.filter6_rules: list[dict] = [] # /ipv6/firewall/filter(ROS 7.18 起 IPv6 也能 FastTrack) self.has_ipv6 = False # 路由器上有没有 IPv6 公网地址 self._sample_prev: dict[str, tuple[float, int, int]] = {} @@ -98,13 +99,14 @@ class RosPoller: ("pools", "/ip/pool"), ("servers", "/ip/dhcp-server"), ("dhcpc", "/ip/dhcp-client"), ("pppoe", "/interface/pppoe-client"), ("mangle", "/ip/firewall/mangle"), ("mangle6", "/ipv6/firewall/mangle"), ("ipset", "/ip/settings"), - ("filter", "/ip/firewall/filter"))} + ("filter", "/ip/firewall/filter"), ("filter6", "/ipv6/firewall/filter"))} self._apply(ifaces, arp, leases, addrs, routes, nb6, addrs6) self._apply_status(ifaces, addrs, leases, routes, extra) self.mangle = [{**r, "_family": "ipv4"} for r in extra["mangle"] or []] + \ [{**r, "_family": "ipv6"} for r in extra["mangle6"] or []] self.ip_settings = extra["ipset"] if isinstance(extra["ipset"], dict) else {} self.filter_rules = extra["filter"] if isinstance(extra["filter"], list) else None + self.filter6_rules = extra["filter6"] if isinstance(extra["filter6"], list) else [] self.has_ipv6 = any(_global_v6(a.get("address")) and a.get("disabled") != "true" for a in addrs6 or []) # 全部成功后再更新展示用的状态,失败时保留上一次的结果 @@ -307,6 +309,10 @@ class RosPoller: ft_limited = bool(ft_rules) and all(r.get("connection-bytes") for r in ft_rules) if fasttrack and not ft_limited: issues.append("fasttrack") + # IPv6 的 FastTrack 没有 ipv4-fasttrack-active 这样的状态位,直接看规则:启用了且没限定 connection-bytes 就提示 + if any(r.get("action") == "fasttrack-connection" and r.get("disabled") != "true" and not r.get("connection-bytes") + for r in self.filter6_rules): + issues.append("fasttrack6") return {"checked": True, "port": port, "target": ref[0] if ref else None, "rules": rules, "issues": issues, "fasttrack": fasttrack, "fasttrack_limited": fasttrack and ft_limited} diff --git a/backend/tests/test_ros_poller.py b/backend/tests/test_ros_poller.py index 4cb8f89..e549138 100644 --- a/backend/tests/test_ros_poller.py +++ b/backend/tests/test_ros_poller.py @@ -112,6 +112,14 @@ def test_fasttrack_check_respects_connection_bytes(monkeypatch): poller.poll() chk = poller.check() assert "fasttrack" not in chk["issues"] and chk["fasttrack_limited"] + # IPv6 的 FastTrack 规则同样要限定 connection-bytes + rule6 = {".id": "*3", "action": "fasttrack-connection", "chain": "forward", "disabled": "false"} + monkeypatch.setitem(ROS, "/rest/ipv6/firewall/filter", [rule6]) + poller.poll() + assert "fasttrack6" in poller.check()["issues"] + monkeypatch.setitem(ROS, "/rest/ipv6/firewall/filter", [{**rule6, "connection-bytes": "10000-0"}]) + poller.poll() + assert "fasttrack6" not in poller.check()["issues"] # 读不到 filter 表(只读账号没权限):按旧办法提示 monkeypatch.delitem(ROS, "/rest/ip/firewall/filter") poller.poll() diff --git a/docs/RouterOS配置.md b/docs/RouterOS配置.md index d98e24e..aea8542 100644 --- a/docs/RouterOS配置.md +++ b/docs/RouterOS配置.md @@ -73,7 +73,27 @@ 这样每条连接的前 10KB 走正常路径(会被嗅探),之后照常 FastTrack。 -**IPv6**:IPv6 流量需要在 `/ipv6 firewall mangle` 下再加一份相同的规则(DNS 嗅探的两条也一样)。 +IPv6 也开了 FastTrack(ROS 7.18 起支持)时同样处理: + +```routeros +/ipv6 firewall filter set [find action=fasttrack-connection] connection-bytes=10000-0 +``` + +### IPv6(路由器开了 IPv6 时必须做) + +国内宽带默认下发 IPv6,抖音、B 站、淘宝等大流量应用会优先走 IPv6。IPv6 的 DNS 应答和 HTTPS / QUIC 握手只经过 `/ipv6 firewall mangle`,只配 IPv4 规则时这部分流量只能按 IP 归类(大多落进「国内网络」)。在 IPv6 下再加一份同样的 4 条规则: + +```routeros +/ipv6 firewall mangle add chain=output protocol=udp src-port=53 action=sniff-tzsp sniff-target=192.168.88.10 sniff-target-port=37008 comment=ROSDPI-DNS +/ipv6 firewall mangle add chain=forward protocol=udp src-port=53 action=sniff-tzsp sniff-target=192.168.88.10 sniff-target-port=37008 comment=ROSDPI-DNS +/ipv6 firewall mangle add chain=forward protocol=tcp dst-port=443 connection-bytes=0-4000 action=sniff-tzsp sniff-target=192.168.88.10 sniff-target-port=37008 comment=ROSDPI-SNI +/ipv6 firewall mangle add chain=forward protocol=udp dst-port=443 connection-bytes=0-4000 action=sniff-tzsp sniff-target=192.168.88.10 sniff-target-port=37008 comment=ROSDPI-SNI +``` + +- 镜像包可以照样发到 ROS-DPI 的 **IPv4 地址**,ROS-DPI 不需要 IPv6 地址;ROS 提示 `sniff-target` 无效时改填 ROS-DPI 的 IPv6 地址(接收端口同时监听 IPv4 / IPv6)。 +- 流量采集不用另配:`/ip traffic-flow` 同时导出 IPv4 和 IPv6 的流。 +- LAN 的 IPv6 前缀:配了只读 API 会自动加入;没配时在 系统设置 → 网络 手动填写。 +- **系统设置 → 接入自检** 会检查:路由器有 IPv6 公网地址却没有 IPv6 嗅探规则、IPv4 / IPv6 的 FastTrack 没有限定 connection-bytes,都会提示并给出要执行的命令。 ## 4. 只读 API 账号(可选) diff --git a/frontend/src/i18n/en.js b/frontend/src/i18n/en.js index de0d126..d266b7f 100644 --- a/frontend/src/i18n/en.js +++ b/frontend/src/i18n/en.js @@ -178,6 +178,7 @@ export default { no_dns: 'No working DNS sniffing rule.', no_sni: 'No working SNI rule: HTTPS / QUIC handshakes never reach ROS-DPI.', no_quic: 'Only a TCP SNI rule; UDP 443 (QUIC / HTTP3) is missing, so QUIC-heavy apps such as Douyin and YouTube are not identified.', fasttrack: 'FastTrack is on and its rule has no connection-bytes limit: fast-tracked connections skip mangle, so handshakes are not mirrored and much HTTPS falls back to IP-only buckets. The command below keeps the first 10 KB of each connection off FastTrack; CPU cost barely changes.', + fasttrack6: 'IPv6 FastTrack is on (/ipv6 firewall filter) without a connection-bytes limit: handshakes of IPv6 connections are not mirrored. The command below keeps the first 10 KB of each connection off FastTrack as well.', no_ipv6: 'The router has public IPv6 addresses but sniff rules exist for IPv4 only: DNS answers and HTTPS / QUIC handshakes over IPv6 are invisible. The commands below add the same rules under /ipv6 firewall mangle; if sniff-target is rejected, use the IPv6 address of ROS-DPI.', }, }, diff --git a/frontend/src/i18n/zh.js b/frontend/src/i18n/zh.js index 1fe9e6a..0fca177 100644 --- a/frontend/src/i18n/zh.js +++ b/frontend/src/i18n/zh.js @@ -178,6 +178,7 @@ export default { no_dns: '没有生效的 DNS 嗅探规则。', no_sni: '没有生效的 SNI 规则:HTTPS / QUIC 握手到不了 ROS-DPI。', no_quic: '只有 TCP 的 SNI 规则,缺少 UDP 443(QUIC / HTTP3),抖音、YouTube 等大量走 QUIC 的流量认不出来。', fasttrack: 'FastTrack 已启用,且规则没有限定 connection-bytes:被 FastTrack 的连接不经过 mangle,握手包镜像不到,大量 HTTPS 只能按 IP 归到「国内网络」。下方命令让每条连接的前 10KB 不走 FastTrack,CPU 占用几乎不变。', + fasttrack6: 'IPv6 也开了 FastTrack(/ipv6 firewall filter),且没有限定 connection-bytes:IPv6 连接的握手镜像不到。下方命令同样让每条连接的前 10KB 不走 FastTrack。', no_ipv6: '路由器有 IPv6 公网地址,但嗅探规则只有 IPv4 的:走 IPv6 的 DNS 应答和 HTTPS / QUIC 握手都看不到(国内运营商默认下发 IPv6,这部分流量通常很大)。下方命令在 /ipv6 firewall mangle 补一份;若提示 sniff-target 无效,改填 ROS-DPI 的 IPv6 地址。', }, }, diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue index afff155..e7c954a 100644 --- a/frontend/src/views/Settings.vue +++ b/frontend/src/views/Settings.vue @@ -434,6 +434,17 @@ const guide = computed(() => { /ip firewall mangle add chain=forward protocol=tcp dst-port=443 connection-bytes=0-4000 action=sniff-tzsp sniff-target=${ip} sniff-target-port=${p.tzsp} comment=ROSDPI-SNI /ip firewall mangle add chain=forward protocol=udp dst-port=443 connection-bytes=0-4000 action=sniff-tzsp sniff-target=${ip} sniff-target-port=${p.tzsp} comment=ROSDPI-SNI +# 3b. 路由器开了 IPv6 时(国内宽带默认都有):IPv6 的 DNS 应答和握手只经过 /ipv6 firewall mangle,再加一份 +# 镜像包照样发到 ROS-DPI 的 IPv4 地址;ROS 提示 sniff-target 无效时改填 ROS-DPI 的 IPv6 地址 +/ipv6 firewall mangle add chain=output protocol=udp src-port=53 action=sniff-tzsp sniff-target=${ip} sniff-target-port=${p.tzsp} comment=ROSDPI-DNS +/ipv6 firewall mangle add chain=forward protocol=udp src-port=53 action=sniff-tzsp sniff-target=${ip} sniff-target-port=${p.tzsp} comment=ROSDPI-DNS +/ipv6 firewall mangle add chain=forward protocol=tcp dst-port=443 connection-bytes=0-4000 action=sniff-tzsp sniff-target=${ip} sniff-target-port=${p.tzsp} comment=ROSDPI-SNI +/ipv6 firewall mangle add chain=forward protocol=udp dst-port=443 connection-bytes=0-4000 action=sniff-tzsp sniff-target=${ip} sniff-target-port=${p.tzsp} comment=ROSDPI-SNI + +# 3c. 开了 FastTrack 时:让每条连接的前 10KB 不走 FastTrack,握手才镜像得到(没有 FastTrack 规则时这两条什么也不改) +/ip firewall filter set [find action=fasttrack-connection] connection-bytes=10000-0 +/ipv6 firewall filter set [find action=fasttrack-connection] connection-bytes=10000-0 + # 4. 只读 API 账号(可选,用于主机名、WAN 速率) /user group add name=rosdpi policy=read,rest-api,api,!write,!policy,!sensitive /user add name=rosdpi group=rosdpi password=<强密码> address=${ip}` @@ -470,10 +481,9 @@ const fixes = computed(() => { out.push(`/ipv6 firewall mangle add chain=forward protocol=tcp dst-port=443 connection-bytes=0-4000 ${tail}`) out.push(`/ipv6 firewall mangle add chain=forward protocol=udp dst-port=443 connection-bytes=0-4000 ${tail}`) } - if (c.issues.includes('fasttrack')) { - // 每条连接的前 10KB 走正常路径(握手能被镜像),之后照常 FastTrack - out.push('/ip firewall filter set [find action=fasttrack-connection] connection-bytes=10000-0') - } + // 每条连接的前 10KB 走正常路径(握手能被镜像),之后照常 FastTrack + if (c.issues.includes('fasttrack')) out.push('/ip firewall filter set [find action=fasttrack-connection] connection-bytes=10000-0') + if (c.issues.includes('fasttrack6')) out.push('/ipv6 firewall filter set [find action=fasttrack-connection] connection-bytes=10000-0') return [...new Set(out)] }) const fixCopied = ref(false)