OpenGFW for iKuai
=================

基于 Netfilter NFQUEUE 的深度报文检测（DPI）插件。

引擎（OpenGFW）已内嵌进本插件，安装包内只有一个可执行文件 opengfwd：
检测引擎、面板、内核规则管理、自我保护都在同一个进程里。没有子进程，
没有日志管道。

配置文件
--------
引擎的全部行为都在配置文件里，文件格式与上游 OpenGFW 完全一致：

  /etc/opengfw/config.yaml   引擎配置（log / io / workers，外加本插件的 ikuai 段）
  /etc/opengfw/rules.yaml    规则集（name / action / log / modifier / expr）

两个文件都可以直接手改，也可以与任何一份 OpenGFW 配置互换；面板改的就是这两个
文件，所以在哪边改都是同一件事。路径可在应用设置里改。

config.yaml 各段的作用（文件里也有同样的注释）：

  log.level      debug / info / warn / error。等级越低代价越高，见下文「日志」。
  io.local       true = 处理路由器自身流量（本机模式），false = 只处理转发流量。
  io.queueSize   NFQUEUE 队列长度（报文数）。
  io.rst         true = 阻断时发 TCP RST，连接立刻断开而不是等超时。
  workers.*      工作线程数与每流缓冲上限（流卡住时限制内存占用）。

  ikuai.enabled      是否真正拦截，默认 false（只记录不拦截）。
  ikuai.scope        识别范围：assist（默认）/ full，见下文「识别范围」。
  ikuai.queueOnly    流量筛选：all（默认）/ dns / web / custom，见下文「流量筛选」。
  ikuai.queuePorts   queueOnly: custom 时的端口，如 "53,8000-8100"。
  ikuai.filterNoise  组播噪声过滤，默认 true。
  ikuai.failopen     过载保护，默认 true。
  ikuai.bypass       不进入检测队列的 IP/CIDR，空格分隔。

写错的处理方式：段名写错（如 workr）会在日志里列出被忽略的字段，不静默忽略；
已存在的段里值非法（如 log.level: verbose）会直接报错并保留原文件 —— 宁可起不来
让人看见，也不静默换一个值替你把规则跑歪。文件损坏时保留原文件、以默认值启动面板
（面板是修复入口）。

从旧版本升级：首次启动会把旧的 settings.json 转成上面两个文件，旧文件改名保留为
settings.json.migrated，日志会说明。

统计数字的来源
--------------
面板总览上的数字来自两处，都不是估出来的：

  * 「总分析包」——内核交给本插件的报文数，读的是 iKuai iptables 规则自带的计数。
    读不到时不会显示成一个看起来正常的 0。
  * 「已拦截流量包」与各协议的「命中」数字——引擎自己的判定与命中计数。
    本引擎按流（连接）判定，所以这里是**判定次数**而不是逐包数量：用户态拿不到
    "内核丢了多少包"这个数，写一个出来就是编的。
    这些计数与日志等级无关，把日志调到 warn 也不会让它们变成 0。

「全加密代理 (SS / 裸 VMess)」是一张合并的卡：从外面看，Shadowsocks 和裸 VMess
都是"全加密流量"（引擎的 fet 分析器），本插件区分不了这两者，所以不拆分显示。

管理面板
--------
面板用的是上游 OpenGFW 面板（ccqwertyui/luci-app-opengfw）的同一套前端，内置在本插件里；
后端是本插件自己的引擎，不是上游那个 dashboard 程序（它启动引擎前要求 nftables 的内核模块，
而 iKuai 把 NFQUEUE 编进内核、且没有 nftables，在真机上会被它拒绝）。

面板能做的事：

  * 域名规则：按域名拦截/放行，可分别选择拦 DNS、TLS(SNI)、HTTP，可选是否含子域；
  * IP 规则：按 IP 或网段，可选方向（进/出/双向）；
  * 端口规则：按端口（TCP 与 UDP 都支持）；
  * 屏蔽：VMess 类全加密流量、Shadowsocks 类、Trojan 特征、SSH；
  * 引擎启停、重启、热重载；
  * 实时日志（不写盘，只在内存里滚动）与流量透视；
  * 原始配置编辑：直接编辑 config.yaml 与 rules.yaml（与 SSH 改的是同一对文件）；
  * 规则仿真测试：输入域名，看哪条规则会命中；
  * 环境诊断与「安装内核模块」（本固件上会如实回答：NFQUEUE 已内建，无需安装）。

面板的规则会翻译成引擎的表达式规则，写在 rules.yaml 的标记行之后：

  # ==== 以下规则由面板生成（面板每次保存都会重写这一段；自己的规则请写在上面）====

标记行以上的内容面板**逐字保留**（含注释与格式），所以你完全可以一半用面板、一半用
SSH 手写。生成的表达式会先用引擎的编译器校验，写不出来的规则不会落盘。

面板上这几项本引擎没有对应实现，**会被明确说明不生效**（不会假装生效）：

  * DNS 污染（把命中域名解析到指定 IP）：上游由它自带的 DNS 服务实现，本插件未内置；
  * 反诈 HTTP 跳转：同上（上游自带 9881 端口的跳转服务）；
  * 屏蔽模式里的「仅记录」：本引擎的判定只有放行与拦截两种结果。

--------
https://<lan1-ip>:9170/dashboard

默认启用 HTTPS，与 HTTP 共用这一个端口：浏览器访问 http:// 会被 307 跳到同地址的
https，明文请求不会被真正处理（面板登录是 HTTP Basic，明文连接上密码可被链路上
任何人读取）。证书默认用路由器自带的那张（`/usr/openresty/ssl/server.crt` 与
`server.key`），可在应用设置里改路径：

  * 证书续期后自动重新加载，无需重启插件；
  * 读不到证书时自动改用自签名证书并在面板说明原因 —— 连接仍是加密的，只是浏览器
    无法验证身份、会提示不受信任；
  * 用 IP 打开时提示不受信任属正常：内置证书的主体是路由器自己的域名；
  * 在应用设置里把「面板 HTTPS」设为关闭即回到纯 HTTP（此时面板会警告密码明文传输）。

首次登录用默认账号 admin / ikuai8.com（HTTP Basic）。面板能开关流量拦截，
请改掉密码；也可以把用户名改成 off 完全关闭鉴权（不建议）。面板只绑定 LAN1
地址，不会暴露到外网。

拦截默认关闭
------------
安装后不会阻断任何流量。默认规则是「只记录不拦截」，用来先观察识别结果，
确认符合预期后再把 action 改成 block。

两种模式
--------
网关模式（推荐）
  处理转发流量（FORWARD 链），即 LAN 客户端的上网流量。
  路由器自身的管理面不受影响。

本机模式（高级，慎用）
  处理路由器自身流量（INPUT/OUTPUT 链）。
  注意：此模式会接管管理面。它把路由器自己的出入流量交到用户态，一旦插件
  停止消费，队列里的报文会被扣住，管理通道可能随之卡住。插件内置两重保护
  （下文的「过载保护」与自动卸载），但都只是减小影响、不能消除影响。
  本机模式不支持「全量接管」：那会把设备自身全部流量送进插件，实测（1 条规则
  即可）会让自身出网停摆。该组合会被拒绝；旧版本若已保存，启动时会自动改为
  辅助增强并在日志说明。
  仅在需要短时诊断时使用，用完请立即关闭。
  自我保护：开启本机模式后会周期性探测本机出网，连续失败则自动卸下内核规则
  并关闭拦截（无需人工介入即可恢复）。本机模式不跨重启保留。

识别范围
--------
本插件不是要取代爱快内置的应用识别，而是补它的盲区。识别范围决定两者的分工：

辅助增强（默认）
  只处理内核未能识别的流量（appid 为 0 且内核未裁决）。
  判定条件是「内核未识别（appid 为 0）且内核未裁决」。
  爱快内置的应用识别、限速、MAC 绑定上网控制、ping 等功能都不受影响。

  ★ 重要（真机实测）：按域名/DNS 的拦截（例如 gfwlist 这类规则）在辅助增强下【不生效】——
  爱快内核已识别的流不会进入本插件。实测同一份 gfwlist 规则集：
  辅助增强下 80/80 条 DNS 查询全部放行（引擎根本看不到这些流）；
  切到全量接管后 80/80 条被拦、对照域名 0/40 误拦。
  要用域名类规则，请选「全量接管」（且只支持网关模式）。

全量接管（备选，仅网关模式）
  所有转发流量都经过本插件，规...[truncated]
  代价是性能与延迟：每个报文都要送到用户态处理，大流量下 CPU 与延迟会上升；
  同时规则写错的波及面从「内核未识别的那部分」扩大到全部转发流量。
  仅在明确需要时才选，应用设置里可切换。

组播噪声过滤
------------
默认开启。把组播与广播报文排除在检测队列之外：这类报文内核从不参与分类，会持续
占用队列（仅 SSDP/DLNA 的广播就足够占住），排除后不会漏掉任何需要检测的流量。
IPv4 与 IPv6 分别探测（两个协议族的匹配写法不同），若某个协议族不支持，面板会
明确显示为「部分生效」，而不是假装已生效。

过载保护
--------
使用爱快内核自带的 NFQUEUE 过载保护（默认开启）。

不开时用的是内核原生行为：队列一旦打满，后续报文直接被丢弃——在内网里这会
表现为断网。开启后，队列占用超过 90% 时改为放行，报文不再被丢。

请注意这是减小影响，不是免疫：已经进入队列的报文仍然在等待，所以插件若停止
处理，最多仍会有「队列长度」这么多报文被扣住。真正兜底的是插件自身的设计
（读取永不阻塞、异常自动卸载）与这个内核开关一起作用。

该开关是全内核生效的，与其它使用 NFQUEUE 的组件共用；本插件启动拦截时会按
设置置位，停止时还原为原先的值。

处理能力（真机实测，供选型参考）
--------
「全量接管」是把转发报文逐个送进用户态判定，成本按【报文】计，不是按流。实测
（4 核 x86，DNS 类小报文，4310 条域名规则）：

  约 10 万包/秒的极端小包压力下：约 4.5 万包/秒可全额处理且队列不积压（约 1.4 核）；
  压力继续升高时队列开始积压，但内核过载保护会让报文放行而不是丢弃——
  此时表现是「过滤效果下降」，不是「网络中断」。

真实网关流量远低于此：1500 字节满包时 4.5 万包/秒相当于约 540 Mbps，而内网
浏览/视频的包速率通常只有几千到几万包/秒。若你只需要按域名拦截 DNS，建议把
「流量筛选」设为「仅 DNS」—— 那时代价几乎为零，实测与不装插件处于同一量级。

流量筛选（降低开销的主要办法）
--------
决定哪些报文进入本插件：

  全部流量   每个转发报文都送进用户态，代价最高（见上）。
  仅 DNS     只送 53 端口（TCP 与 UDP）。按域名拦截（gfwlist 那种）够用，
             代价几乎为零 —— 这是「只想拦域名」时的推荐值。
  仅网页     只送 80 与 443。按 TLS SNI / HTTP 拦截需要它。
  自定义     自填端口，支持范围（如 8000-8100），TCP 与 UDP 都生效。

注意两件事：

  1. 没被包含的流量【完全不会】被本插件检测。比如把筛选设为「仅 DNS」，任何
     按 TLS SNI 拦截的规则都不会生效 —— 那是流量根本没被看到，不是规则写错。
  2. 筛选与「识别范围」是两个独立维度：前者决定「看多少」，后者决定「对谁负责」
     （辅助增强只管内核未识别的流，全量接管接管全部转发流量）。

日志
--------
等级越低日志越详细、开销越大。引擎对每个报文的判定记录会格式化整份 DNS 属性，
所以等级设为 info/debug 且流量较大时会明显拖慢转发（实测未做限制时吞吐被压到
约 9 千包/秒）。现在有每秒 500 条的上限，超出部分丢弃并计数，面板会提示
「已丢弃 N 条」；排查完建议调回 warn 或 error。

部署前提
--------
以下情况插件根本看不到流量，会直接拒绝启用并说明原因（而不是安静地一条都不
命中，让人误以为"没有匹配流量"）：

- 链路模式为旁路模式或 SD-WAN 网桥：这类流量走二层桥接，不经过 iptables。
- 检测到网桥透传。

加速模式（FASTOFFLOAD）不阻止启用，但会提示：开启后每条流只能看到前若干报文，
依赖整条流行为的检测（如全加密流量判定）会失效。

规则写法
--------
规则为 YAML 列表，每条形如：

  - name: log-dns
    action: allow      # allow / block / drop
    log: true
    expr: dns != nil

常用表达式片段（字段名必须是真实存在的分析器）：

  dns != nil                              DNS 查询
  tls != nil && tls.req.sni != ""         TLS SNI（注意 SNI 在 tls.req 下）
  quic != nil                             QUIC / HTTP3
  http != nil                             HTTP
  ssh != nil / socks != nil               SSH / SOCKS
  wireguard != nil / openvpn != nil       WireGuard / OpenVPN
  fet != nil && fet.yes                   全加密流量（Shadowsocks 类）
  trojan != nil                           Trojan 特征

注意：引擎不会拒绝写错的字段名（规则会静默永不命中），面板会在保存时校验，
请以面板的校验结果为准。例如并不存在 ss / vmess 分析器，全加密流量检测是 fet。

绕过名单
--------
填写不进入检测队列的 IP 或 CIDR，多个用逗号或换行分隔。规则会插在 NFQUEUE
之前，这些地址直接放行，不消耗检测资源。

安全须知
--------
- 开启拦截前，请先用「只记录」规则观察日志。
- 关闭拦截时会自动卸下内核挂载，不留残余规则；异常退出后重新启动也会清理。
- 若上次未正常退出（拦截仍处于挂载状态），本次开机不会自动恢复拦截。
- 本机模式不跨重启保留，每次都需要手动开启。
- 应用设置里的改动（识别范围、过载保护）需改 config.yaml 后重启插件生效；面板里的改动
  立即生效。

与爱快内置功能的关系
--------------------
在网络流量的处理顺序上，本插件的钩子位于爱快自身功能链之后：

  转发路径：ACL_MAC（MAC 绑定）→ 连接数限制 → 限速 → 防火墙 → 本插件

所以辅助增强模式不会绕过或削弱这些功能。全量接管的代价见上文。

日志文件
--------
面板「日志」页显示引擎识别结果（命中规则、解析到的协议字段）。
文件：<插件目录>/.pkg/log/run.log（插件自身状态）、.pkg/log/engine.log（引擎输出），
面板的「日志」页读的就是它们。
