#!/bin/sh
# ============================================================================
# opengfw 第二轮排查：你的 SS 流量到底有没有经过插件的钩子
#
# 用法（在路由上执行）：
#     sh /tmp/ogdiag2.sh            # 面板端口/账号/口令自动从运行进程参数里读
#     sh /tmp/ogdiag2.sh <面板口令>
#
# 第 ④ 步采样 25 秒 —— 请在采样期间用客户端连一次 SS 并打开一个网页。
# 跑完把整段输出发回。脚本不会打印口令。
# ============================================================================

J=/usr/sbin/jq
[ -x "$J" ] || J=$(command -v jq 2>/dev/null)
PW_ARG="$1"
SRV=hgc.nodetect.com
SPORT=54454

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); RUL=$(argof -rules); EG=$(argof -engine-log)
WUSER=$(argof -web-user); WPASS=$(argof -web-password); TLSARG=$(argof -web-tls)
[ -n "$WUSER" ] || WUSER=$(envof "$ENVF" OPENGFW_WEB_USER)
[ -n "$WPASS" ] || WPASS=$(envof "$ENVF" OPENGFW_WEB_PASSWORD)
[ -n "$TLSARG" ] || TLSARG=$(envof "$ENVF" OPENGFW_WEB_TLS)
[ -n "$PW_ARG" ] && WPASS="$PW_ARG"
HOST="${LISTEN%:*}"; PORT="${LISTEN##*:}"
case "$HOST" in ""|0.0.0.0|"::"|"[::]") HOST=127.0.0.1 ;; esac
[ -n "$PORT" ] || PORT=9170
[ "$TLSARG" = "off" ] && SCHEME=http || SCHEME=https
BASE="$SCHEME://$HOST:$PORT"
api() {
  if [ -n "$WUSER" ]; then curl -sk -m 15 -u "$WUSER:$WPASS" "$BASE$1"
  else curl -sk -m 15 "$BASE$1"; fi
}
qcnt() {
  for C in INPUT OUTPUT FORWARD; do
    iptables -w -L "$C" -v -n -x 2>/dev/null | grep NFQUEUE
  done | awk '{s+=$1} END {print s+0}'
}

# 生效路径（启动参数里的可能是老版本写进去的默认值，1.12 起会自动重定向）
EFF_CFG=$(api /api/v1/status | $J -r '.config_path' 2>/dev/null)
[ -n "$EFF_CFG" ] && CFG="$EFF_CFG"
EFF_RUL=$(api /api/v1/status | $J -r '.rules_path' 2>/dev/null)
[ -n "$EFF_RUL" ] && RUL="$EFF_RUL"

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

echo "① 引擎日志的真实路径（先排除"上一轮读错文件"）"
echo "  -engine-log 参数: ${EG:-（未提供，引擎日志只在内存）}"
if [ -n "$EG" ]; then
  ls -la "$EG" 2>&1 | sed 's/^/    /'
  [ -f "$EG" ] && echo "    行数 $(wc -l < "$EG")  字节 $(wc -c < "$EG")"
fi
echo "  候选日志目录:"
ls -la "$APPDIR/.pkg/log/" 2>/dev/null | tail -n +2 | sed 's/^/    /'
ls -la "$APPDIR/log/" 2>/dev/null | tail -n +2 | sed 's/^/    /'
hr

echo "② 引擎自己的计数（不看日志文件也能判断它有没有在判定）"
api /api/status | $J -c '.stats' 2>/dev/null || echo "  （读不到）"
echo "  字段含义: total_packets=内核交给插件的报文数  decided_streams=引擎给出判定的流数"
echo "            blocked_packets=被拦截数  matches=各协议识别命中数"
hr

echo "③ SS 服务器与当前连接（看流量是从局域网来、还是路由自己发的）"
IP=""
for r in 1 2 3; do
  IP=$(ping -c1 -W3 "$SRV" 2>/dev/null | sed -n 's/^PING [^(]*(\([0-9.]*\)).*/\1/p' | head -1)
  [ -n "$IP" ] && break
  IP=$(nslookup "$SRV" 2>/dev/null | awk '/^Address: /{print $2}' | tail -1)
  [ -n "$IP" ] && break
  sleep 1
done
echo "  $SRV → ${IP:-解析失败}    端口 $SPORT"
echo "  当前 :$SPORT 的连接（空=此刻没有）："
netstat -tn 2>/dev/null | grep ":$SPORT" | sed 's/^/    /'
if [ -f /proc/net/nf_conntrack ]; then
  grep "dport=$SPORT" /proc/net/nf_conntrack 2>/dev/null | head -5 | sed 's/^/    /'
fi
echo "  说明：源地址是 192.168.9.1（路由自身）→ 流量走 OUTPUT，插件的网关模式看不到；"
echo "        源地址是局域网客户端 → 走 FORWARD，插件应该能看到。"
hr

# ── ④ 把日志等级调到 debug（临时）后采样 ─────────────────────────────────
echo "④ 采样 25 秒（期间把引擎日志临时调到 debug 以便看清判定）"
BAK="/tmp/ogcfg.bak.$$"; NEW="/tmp/ogcfg.new.$$"; JB="/tmp/ogjb.$$"; JN="/tmp/ogjn.$$"
HAVE_CFG=0
if [ -n "$CFG" ] && [ -f "$CFG" ]; then
  cp "$CFG" "$BAK" 2>/dev/null && HAVE_CFG=1
  sed 's/^\([[:space:]]*level:\).*/\1 debug/' "$CFG" > "$NEW"
  if [ "$HAVE_CFG" = "1" ]; then
    $J -R -s '{yaml:.}' < "$BAK" > "$JB" 2>/dev/null
    $J -R -s '{yaml:.}' < "$NEW" > "$JN" 2>/dev/null
    echo "  改日志等级 → $(curl -sk -m 15 -u "$WUSER:$WPASS" -H 'Content-Type: application/json' -X POST --data-binary @"$JN" "$BASE/api/raw/config" | $J -r '.message // .error' 2>/dev/null)"
  fi
else
  echo "  ⚠️ 读不到配置文件路径，无法临时调级别（继续采样，靠 stats 判断）"
fi

SSDUMP="/tmp/ogss.$$"; : > "$SSDUMP"
Q1=$(qcnt)
echo "  现在请连一次 SS 并打开一个网页……"
i=0
while [ $i -lt 5 ]; do
  sleep 5
  netstat -tn 2>/dev/null | grep ":$SPORT" >> "$SSDUMP"
  [ -f /proc/net/nf_conntrack ] && grep "dport=$SPORT" /proc/net/nf_conntrack 2>/dev/null >> "$SSDUMP"
  i=$((i + 1))
done
Q2=$(qcnt)
echo "  队列: $Q1 → $Q2   增量 $((Q2 - Q1))"
echo "  采样期间抓到的 :$SPORT 连接（去重）："
sort -u "$SSDUMP" | sed 's/^/    /' | head -12
[ -s "$SSDUMP" ] || echo "    （一条都没有 —— 说明这台设备上没有到 $SRV:$SPORT 的连接）"
echo "  采样后 stats:"
api /api/status | $J -c '.stats' 2>/dev/null | sed 's/^/    /'
hr

echo "⑤ 引擎判定记录（debug 级别）"
if [ -n "$EG" ] && [ -f "$EG" ]; then
  echo "  文件大小 $(wc -c < "$EG") 字节"
  echo "  --- 含 SS 服务器地址 / 端口 / fet / shield 的行 ---"
  grep -aE "${IP:-@none@}|:$SPORT|fet|shield" "$EG" 2>/dev/null | tail -15 | cut -c1-190 | sed 's/^/    /'
  echo "  --- 末尾 25 行（看引擎有没有在处理流）---"
  tail -25 "$EG" 2>/dev/null | cut -c1-190 | sed 's/^/    /'
else
  echo "  ⚠️ 读不到引擎日志文件（见 ①）"
fi
hr

# ── 还原日志等级 ────────────────────────────────────────────────────────
if [ "$HAVE_CFG" = "1" ]; then
  echo "⑥ 还原日志等级"
  echo "  $(curl -sk -m 15 -u "$WUSER:$WPASS" -H 'Content-Type: application/json' -X POST --data-binary @"$JB" "$BASE/api/raw/config" | $J -r '.message // .error' 2>/dev/null)"
fi
rm -f "$BAK" "$NEW" "$JB" "$JN" "$SSDUMP" 2>/dev/null
hr
echo "本轮结束。把整段输出发回。"
