#!/bin/sh
# ============================================================================
# opengfw 第三轮：判定「内核是不是在握手之后就把这条流拿走了」
#
# 关键判据（本次要测的）：
#   对同一个端口，比较
#     ① 经过 FORWARD 的报文总数
#     ② 其中「内核未分类(appid 0)」的报文数
#   如果 ② 明显小于 ①，说明内核对这条流做过分类 ——
#   而插件只被允许看「未分类」的报文，于是带数据的那部分它根本收不到。
#
# 用法（在路由上执行）：
#     sh /tmp/ogdiag3.sh                    # 只测量（测完自动移除临时规则）
#     sh /tmp/ogdiag3.sh full               # 顺便把识别范围改成「全量接管」
#     sh /tmp/ogdiag3.sh assist             # 改回「辅助增强」
#
# 测量那 20 秒里请用客户端连一次 SS 并打开网页。脚本不会打印口令，跑完自动清理。
# ============================================================================

J=/usr/sbin/jq
[ -x "$J" ] || J=$(command -v jq 2>/dev/null)
CMD="$1"
SPORT=54454
CNT=OGCNT
WIN=20

hr() { echo "──────────────────────────────────────────────"; }

APPDIR=""
for d in /etc/log/apps/opengfw-*; do [ -d "$d" ] && APPDIR="$d"; done
[ -n "$APPDIR" ] || { echo "❌ 没找到已安装的 opengfw 应用"; exit 0; }

VPID=""
for p in /proc/[0-9]*; do
  [ -r "$p/cmdline" ] || continue
  if tr '\0' ' ' < "$p/cmdline" 2>/dev/null | grep -q 'opengfwd'; then VPID=${p#/proc/}; break; fi
done
argof() {
  [ -n "$VPID" ] || return
  tr '\0' '\n' < "/proc/$VPID/cmdline" 2>/dev/null | awk -v f="$1" 'p==f{print;exit}{p=$0}'
}
envof() { [ -r "$1" ] && sed -n "s/^$2=//p" "$1" | head -1; }
ENVF="$APPDIR/.pkg/app/.env"
LISTEN=$(argof -listen); CFG=$(argof -config)
WUSER=$(argof -web-user); WPASS=$(argof -web-password)
[ -n "$WUSER" ] || WUSER=$(envof "$ENVF" OPENGFW_WEB_USER)
[ -n "$WPASS" ] || WPASS=$(envof "$ENVF" OPENGFW_WEB_PASSWORD)
[ -n "${2:-}" ] && WPASS="$2"
HOST="${LISTEN%:*}"; PORT="${LISTEN##*:}"
case "$HOST" in ""|0.0.0.0|"::"|"[::]") HOST=127.0.0.1 ;; esac
[ -n "$PORT" ] || PORT=9170
BASE="https://$HOST:$PORT"
api() { curl -sk -m 15 -u "$WUSER:$WPASS" "$BASE$1"; }
EFF_CFG=$(api /api/v1/status | $J -r '.config_path' 2>/dev/null)
[ -n "$EFF_CFG" ] && CFG="$EFF_CFG"

cleanup() {
  iptables -w -D FORWARD -j $CNT 2>/dev/null
  iptables -w -F $CNT 2>/dev/null
  iptables -w -X $CNT 2>/dev/null
  echo "  [清理] 临时计数链已移除"
}
trap cleanup EXIT INT TERM

echo "opengfw 第三轮测量   $(date '+%Y-%m-%d %H:%M:%S')"
echo "面板: $BASE   引擎 pid: ${VPID:-未运行}"
echo "生效配置: ${CFG:-未读到}"
hr

# ── 改识别范围（可选）────────────────────────────────────────────────────
if [ "$CMD" = "full" ] || [ "$CMD" = "assist" ]; then
  if [ -z "$CFG" ] || [ ! -f "$CFG" ]; then
    echo "❌ 读不到配置文件，无法改识别范围"
  else
    cp "$CFG" /tmp/ogcfg.bak.$$
    sed "s/^\([[:space:]]*scope:\).*/\1 $CMD/" "$CFG" > /tmp/ogcfg.new.$$
    $J -R -s '{yaml:.}' < /tmp/ogcfg.new.$$ > /tmp/ogjb.$$
    echo "改识别范围 → $CMD"
    if [ "$CMD" = "full" ]; then
      echo "  ⚠️ 全量接管：所有流量都会经过插件（实测约 1.4 核 / 3.7 万包每秒）。"
      echo "     这也是让「域名 / 协议屏蔽」类规则生效的前提。"
    fi
    echo "  $(curl -sk -m 15 -u "$WUSER:$WPASS" -H 'Content-Type: application/json' -X POST --data-binary @/tmp/ogjb.$$ "$BASE/api/raw/config" | $J -r '.message // .error' 2>/dev/null)"
    rm -f /tmp/ogcfg.bak.$$ /tmp/ogcfg.new.$$ /tmp/ogjb.$$
    api /api/status | $J -r '"  当前：引擎=" + (.engine.running|tostring) + " 拦截=" + (.fw_active|tostring) + " 识别范围=" + .settings.scope'
    hr
  fi
fi

echo "① 当前状态"
api /api/status | $J -r '"  引擎=" + (.engine.running|tostring) + " 拦截=" + (.fw_active|tostring)
  + " 识别范围=" + .settings.scope + " 流量筛选=" + .settings.queue_only + " 日志等级=" + .settings.log_level'
echo "  状态里没显示 hooks/verdict 说明：插件只处理「内核未分类」的流"
echo "  stats: $(api /api/status | $J -c '.stats')"
hr

echo "② 挂临时计数链（只计数，不改走向；结束自动移除）"
iptables -w -N $CNT 2>/dev/null
# 无 -j 的规则只累加计数，然后继续往下走
iptables -w -A $CNT -p tcp --dport $SPORT
iptables -w -A $CNT -p tcp --dport $SPORT -m appmark --appid 0
iptables -w -I FORWARD 1 -j $CNT
echo "  已挂在 FORWARD 第 1 位"
hr

echo "③ 采样 ${WIN} 秒 —— 现在请连一次 SS 并打开网页"
sleep "$WIN"
echo
echo "④ 结果"
iptables -w -L $CNT -v -n -x | tail -n +3 | while read -r line; do
  set -- $line
  echo "  $1 包  ← $(echo "$line" | sed 's/.*tcp //' | cut -c1-80)"
done
iptables -w -L $CNT -v -n -x | tail -n +3 | awk 'NR==1{a=$1} NR==2{b=$1} END{
  printf "  合计 %s 包；其中内核未分类 %s 包。\n", a, b
  if (a+0 == 0) print "  ⚠️ 没有抓到该端口的报文（SS 没在用？端口不是 54454？）"
  else if (b+0 < a+0) {
    printf "  ❗ 差额 %d 包 = 内核对这条流做过分类 → 插件收不到这部分（assist 的规则本来如此）\n", a-b
    print "     → 结论：要拦这条流，识别范围必须是【全量接管 full】"
  } else {
    print "  → 该端口的报文全部是「未分类」，插件应当能看到它们（那问题就不在识别范围上）"
  }
}'
echo "  引擎 stats（对比 ①）:"
api /api/status | $J -c '.stats' | sed 's/^/    /'
hr
echo "说明：计数链上的数字是「经过 FORWARD 的报文」，与插件队列计数不同；"
echo "      插件只收到其中「内核未分类」的那部分。"
echo "本轮结束，把整段输出发回。"
