Skip to content

fix(trace): [4/4] 实际连接按链路聚合,消除新旧连接并存时的误报#1

Merged
crhan merged 4 commits into
mainfrom
improve/trace-section4-aggregate
Jun 5, 2026
Merged

fix(trace): [4/4] 实际连接按链路聚合,消除新旧连接并存时的误报#1
crhan merged 4 commits into
mainfrom
improve/trace-section4-aggregate

Conversation

@crhan

@crhan crhan commented Jun 5, 2026

Copy link
Copy Markdown
Owner

动机

改路由规则(如 DOMAIN-SUFFIX,xxx,DIRECTproxy)后的过渡态下,同一域名会同时存在新连接(走新出口)和旧连接(建连时定死的旧链路仍在 TLS 复用)。旧版 proxyctl trace[4/4] 实际连接 逐条平铺所有活跃连接、却只取其中一条与规则预测对比,于是出现自相矛盾的输出:

  ⚠ 预测出口 proxy,实际出口 DIRECT      ← 只挑了一条旧 DIRECT 连接判定
  ---
  aicodewith.com:443  电信专用(直连)  ...  ← 详情里 proxy 和 DIRECT 都有
  aicodewith.com:443  DIRECT         ...

用户无法理解"预测/实际出口"是什么、为什么列表里两种都有。

根因

  1. 只挑一条做二元判定 —— 多链路并存时必然误判。
  2. "出口"一词两处指代不同层级 —— 告警行取 chains[-1](链路入口组名 proxy),详情行取 chains[0](末端节点名 电信专用),同词不同义。

改动

  • [4/4] 改为按完整链路聚合:相同链路的连接合并计数、累加流量,不再逐条平铺。
  • 判定改用集合包含(预测组名是否出现在链路任意一跳),消除 chains 取头/取尾的歧义。
  • 按命中情况给三种明确结论:全部一致 / 全部未命中(规则可能未生效或被前置规则截胡)/ 新旧并存(旧连接会随超时自然断开,刷新后即全部走预测出口)。
  • 连接采集改为 host 精确匹配优先,仅在拿不到 host(典型 fake-ip 模式)时回退按 destinationIP 匹配,避免 Cloudflare 等共享 IP 把同 IP 其他站点的连接误算进当前域名。

新输出:

[4/4] 实际连接
  (mihomo 当前活跃连接 2 条)
  应走(规则预测): proxy
  实走:
    ✓ proxy → proxy-tuic → 电信专用(直连)  ·  2 条  ↑1.1K ↓6.3K
  ✓ 结论: 全部 2 条都走 proxy,与规则一致

测试

  • 补 5 个单测覆盖三种结论 + host 优先 + IP 回退。
  • 全套 pytest 805 passed。

改规则后的过渡态下,同一域名会同时存在新连接(走新出口)和旧连接
(建连时定死的旧链路仍在复用)。旧版 [4/4] 逐条平铺所有活跃连接、却只取
其中一条与规则预测对比,于是出现"告警说实际走 DIRECT、底下列表又 proxy/
DIRECT 都有"的自相矛盾,看不懂。

根因有二:
1. 只挑一条做二元判定,多链路并存时必然误判;
2. "出口"一词两处指代不同层级——告警行取 chains[-1](入口组),详情行取
   chains[0](末端节点)。

重写为:
- 相同链路的连接合并计数、累加流量,不再逐条平铺;
- 判定改用集合包含(预测组名是否出现在链路任意一跳),消除 chains 取头/取尾
  的歧义;
- 按命中情况分三种结论:全部一致 / 全部未命中(规则可能未生效)/ 新旧并存
  (旧连接会随超时自然断开,刷新后即全部走预测出口);
- 连接采集改为 host 精确匹配优先,仅在无 host(fake-ip)时回退按 IP 匹配,
  避免 Cloudflare 等共享 IP 把同 IP 别站连接误算进来。

补 5 个单测覆盖三种结论 + host 优先 + IP 回退。
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@crhan

crhan commented Jun 5, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ca0dced77b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/proxyctl/trace.py Outdated
Comment on lines +520 to +521
elif di and di in resolved_ips:
ip_conns.append(c)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict IP fallback to hostless connections

When no exact host match is found, this branch still collects every connection whose destinationIP is in resolved_ips, even if metadata.host is populated with a different domain. For CDN/shared-IP targets where the traced site has no active host-matched socket, [4/4] will still attribute another site's active connection on the same IP to the traced domain, so the Cloudflare-style false positive described by this change remains. The IP fallback should only include connections where the engine did not provide a host.

Useful? React with 👍 / 👎.

[4/4] 的 IP 回退分支原本只要 destinationIP 命中就收,host 非空但指向
别站的连接(同一 Cloudflare 共享 IP)在"目标站无 host 匹配连接"时仍会
被误算成本域名——上一版想消除的共享 IP 误报在这个边角没消干净。

IP 回退加 `not h` 限制:只回退真正拿不到 host 的连接(fake-ip 场景),
host 非空的别站连接彻底排除。补回归测试覆盖该边角。

Codex review (PR#1) P2 — Restrict IP fallback to hostless connections.
@crhan

crhan commented Jun 5, 2026

Copy link
Copy Markdown
Owner Author

@codex review — 已按建议把 IP 回退限定为 hostless 连接(not h),并补了回归测试覆盖

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5bd00bc31b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/proxyctl/trace.py Outdated
Comment on lines +516 to +520
h = m.get("host", "")
di = m.get("destinationIP", "")
if (h == domain or h.endswith("." + domain)
or (di and di in resolved_ips)):
domain_conns.append(c)
if domain_conns:
if h == domain or h.endswith("." + domain):
host_conns.append(c)
elif not h and di and di in resolved_ips:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use sniffHost before IP fallback

When TUN/sniffer traffic is present, mihomo can leave metadata.host empty while reporting the observed SNI/HTTP host in metadata.sniffHost; in that case this branch treats not h as true and falls back to destinationIP. For shared-IP destinations, trace aicodewith.com can still count an active other-site.com connection shaped like {host: "", sniffHost: "other-site.com", destinationIP: "104.26.4.164"}, so the shared-IP false positive survives unless sniffHost is folded into the host check before allowing IP fallback.

Useful? React with 👍 / 👎.

mihomo 在 sniffer/TUN 模式下可能把 metadata.host 留空、真实域名放在
sniffHost(嗅探到的 SNI/HTTP Host)。上一版用 `not h` 判断是否 IP 回退,
sniffHost 场景 host 为空仍会触发回退,别站连接
{host:"", sniffHost:"other-site.com"} 在共享 IP 下又被误算成本域名。

改为有效 host = host or sniffHost:host 匹配与 IP 回退都基于它,
只有 host 和 sniffHost 都空(纯 IP)才回退。顺带修了 sniffer 模式下
目标连接 host 为空、真实域名在 sniffHost 时被漏识别的问题。
补两个回归测试(sniffHost 命中纳入 / sniffHost 别站排除)。

Codex review (PR#1) P2#2 — Use sniffHost before IP fallback.
@crhan

crhan commented Jun 5, 2026

Copy link
Copy Markdown
Owner Author

@codex review — 已把 sniffHost 折进有效 host(host or sniffHost),IP 回退仅在 host/sniffHost 都为空时触发,并补了回归测试

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dce94d260b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/proxyctl/trace.py Outdated
h = m.get("host", "")
# sniffer/TUN 模式下 mihomo 可能把 host 留空、真实域名放在
# sniffHost(嗅探到的 SNI / HTTP Host)——取二者作为有效 host
h = m.get("host", "") or m.get("sniffHost", "")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match sniffHost even when host is populated

When mihomo reports both fields (for example host is the destination IP while sniffHost is the SNI/HTTP host), this or keeps only host, so a real sniffHost == domain connection is neither added to host_conns nor allowed to use the IP fallback because h is non-empty. In that sniffer/TUN case, proxyctl trace <domain> can still print “无活跃连接” or miss the route aggregation for the active target connection; consider checking host and sniffHost independently before deciding whether both are absent.

Useful? React with 👍 / 👎.

上一版用 `host or sniffHost` 合成有效 host,在 sniffer/TUN 把 host 填成
目的 IP、真实域名落在 sniffHost 时,host 非空(是 IP)会遮蔽 sniffHost:
{host:"104.26.4.164", sniffHost:"aicodewith.com"} 这条目标连接既不匹配
域名、又因 h 非空进不了 IP 回退 → 被漏掉,trace 误报"无活跃连接"。

改为两字段独立判断:任一匹配 domain 即纳入;只有 host 和 sniffHost 都
缺失(纯 IP)才按目的 IP 回退。彻底消除字段互相遮蔽。补回归测试。

Codex review (PR#1) P2#3 — Match sniffHost even when host is populated.
@crhan

crhan commented Jun 5, 2026

Copy link
Copy Markdown
Owner Author

@codex review — 已改为 host 与 sniffHost 独立判断(任一匹配即纳入,两者皆空才回退 IP),修掉 host=IP 遮蔽 sniffHost 的漏报,补了回归测试

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@crhan crhan merged commit c266b42 into main Jun 5, 2026
9 checks passed
@crhan crhan deleted the improve/trace-section4-aggregate branch June 5, 2026 05:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant