Verify steps
Mihomo version
Alpha HEAD c59c99a0。此 bug 自 2022-07 的 b23a0710 起就存在——那段代码从第一天起写错,从未正常工作过。
Alpha HEAD c59c99a0. The bug has existed since b23a0710 (2022-07) — the code has never been correct since it was first written.
What OS are you seeing the problem on?
与平台无关。问题出在分享链接导入逻辑,不涉及任何平台特定代码。
Platform-independent. The bug is in the share-link conversion layer, not in any platform-specific code.
Mihomo config
最小复现用例——一个 v2rayN 风格的 base64 编码 VMess 分享链接,net: h2 且带有伪装 host:
Minimal reproducer — a v2rayN-style base64-encoded VMess share link that declares net: h2 with a disguise host:
proxies:
- vmess://eyJ2IjoiMiIsInBzIjoiZGVtby1oMi1ob3N0LWxvc3QiLCJhZGQiOiJzZXJ2ZXIuZXhhbXBsZS5jb20iLCJwb3J0IjoiNDQzIiwiaWQiOiJiODMxMzgxZC02MzI0LTRkNTMtYWQ0Zi04Y2RhNDhiMzA4MTEiLCJhaWQiOiIwIiwic2N5IjoiYXV0byIsIm5ldCI6ImgyIiwidHlwZSI6Im5vbmUiLCJob3N0IjoiY2RuLmV4YW1wbGUuY29tIiwicGF0aCI6Ii9ncnBjIiwidGxzIjoidGxzIn0=
上面的 base64 负载解码后是:
The base64 payload decodes to:
{"v":"2","ps":"demo-h2-host-lost","add":"server.example.com","port":"443","id":"b831381d-6324-4d53-ad4f-8cda48b30811","aid":"0","scy":"auto","net":"h2","type":"none","host":"cdn.example.com","path":"/grpc","tls":"tls"}
导入后,生成的 proxy h2-opts.host 为空——订阅里指定的伪装域名 cdn.example.com 被静默丢弃。
After import, the resulting proxy has h2-opts.host empty — the intended disguise host cdn.example.com is silently lost.
Mihomo log
不适用——bug 是静默的,没有 error、warning 或 debug 日志。对上述分享链接运行 ConvertsV2Ray,实际得到的 h2-opts map 是:
N/A — the bug is silent. No error, no warning, no debug line. Running ConvertsV2Ray on the link above produces an h2-opts map that looks like:
map[string]any{
"headers": map[string]any{"Host": []string{"cdn.example.com"}},
"path": "/grpc",
}
h2-opts.host 完全缺失;伪装 host 被写进了 HTTP2Options 根本不存在的 headers 键里,被解码器静默丢弃。
h2-opts.host is missing entirely; the disguise host lives inside a key (headers) that HTTP2Options does not have, so the decoder silently discards it.
Description
common/convert/converter.go:333-343 的 case "h2": 分支——也就是老式 v2rayN 风格 base64-JSON VMess 分享链接的解析路径——把伪装 host 写到了错误的 map key 上。结果是:每一个使用 HTTP/2 transport 的 VMess 订阅节点在导入时都会静默丢掉自己的伪装 host。
The case "h2": branch in common/convert/converter.go:333-343 — the legacy v2rayN-style base64-JSON VMess share-link parser — writes the disguise host into the wrong map key. As a result, every VMess subscription node that uses HTTP/2 transport silently loses its disguise host during import.
当前代码 / Current code:
case "h2":
headers := make(map[string]any)
h2Opts := make(map[string]any)
if host, ok := values["host"].(string); ok && host != "" {
headers["Host"] = []string{host} // ← 写进了错误的 map 和错误的 key / wrong map, wrong key
}
h2Opts["path"] = values["path"]
h2Opts["headers"] = headers // ← HTTP2Options 没有 Headers 字段 / HTTP2Options has no Headers field
vmess["h2-opts"] = h2Opts
下游 schema / Downstream schema (adapter/outbound/vmess.go:80-83):
type HTTP2Options struct {
Host []string `proxy:"host,omitempty"`
Path string `proxy:"path,omitempty"`
}
没有 Headers 字段。h2Opts["headers"] 在 common/structure 解码时是一个未知 key(WeaklyTypedInput: true、未启用 ErrorUnused),被静默丢弃,里面的 host 值也随之丢失。
There is no Headers field. h2Opts["headers"] is an unknown key during common/structure decoding (WeaklyTypedInput: true, ErrorUnused not enabled) and is silently discarded; the host value placed inside it is lost with it.
正确的 key 应该是 h2Opts["host"] = []string{host}。
The correct key is h2Opts["host"] = []string{host}.
common/convert/v.go(新式 VMess AEAD / VLESS URL 解析器)里有一个平行 bug,已由 #2555 报告、#2737 修复。两者同源——都是从相邻的 case "http":(HTTP/1.1 camouflage)分支 copy-paste 过来的——但运行在不同的订阅格式上:
The parallel bug in common/convert/v.go (the modern VMess AEAD / VLESS URL parser) was reported as #2555 and fixed in #2737. Both originate from the same copy-paste pattern (cloned from the adjacent case "http": HTTP/1.1 camouflage block), but each runs on a different subscription format:
-
v.go handleVShareLink —— 新式 vmess://...?type=http&... / vless://...?type=http&... URL
-
converter.go case "h2": —— 老式 v2rayN 风格 base64 编码 JSON 的 vmess://<base64>
-
v.go handleVShareLink — modern vmess://...?type=http&... / vless://...?type=http&... URLs
-
converter.go case "h2": — legacy v2rayN-style base64-encoded JSON vmess://<base64>
#2737 没覆盖老式路径,因为入口和编码都不同。本 issue 跟踪老式路径的修复。
#2737 did not cover the legacy path because the entry point and encoding are different. This issue tracks the legacy path fix.
复现步骤 / Reproduction
-
从 Alpha HEAD 构建 mihomo / Build mihomo from Alpha HEAD.
-
导入任何 v2rayN 风格、JSON 里 "net": "h2"(或 "net": "http",converter.go 会把它 remap 成 "h2")且 "host" 非空的订阅。上面给出的最小 config 即可。
Import any v2rayN-style subscription whose JSON has "net": "h2" (or "net": "http", which converter.go remaps to "h2") and a non-empty "host". The minimal config above works.
-
通过 /proxies API 或 config dump 查看加载后的 proxy——h2-opts.host 为空,订阅里指定的伪装 host 消失了。
Inspect the loaded proxy via the /proxies API or a config dump — h2-opts.host is empty; the disguise host from the subscription is gone.
建议修复 / Proposed fix
直接照搬 #2737 对 v.go 平行分支的修复思路:
Direct mirror of the fix applied by #2737 to the parallel v.go branch:
case "h2":
h2Opts := make(map[string]any)
if host, ok := values["host"].(string); ok && host != "" {
h2Opts["host"] = []string{host}
}
if path, ok := values["path"].(string); ok && path != "" {
h2Opts["path"] = path
}
vmess["h2-opts"] = h2Opts
我会准备 PR。
I'll prepare a PR.
Verify steps
Mihomo version
Alpha HEAD
c59c99a0。此 bug 自 2022-07 的b23a0710起就存在——那段代码从第一天起写错,从未正常工作过。Alpha HEAD
c59c99a0. The bug has existed sinceb23a0710(2022-07) — the code has never been correct since it was first written.What OS are you seeing the problem on?
与平台无关。问题出在分享链接导入逻辑,不涉及任何平台特定代码。
Platform-independent. The bug is in the share-link conversion layer, not in any platform-specific code.
Mihomo config
最小复现用例——一个 v2rayN 风格的 base64 编码 VMess 分享链接,
net: h2且带有伪装host:Minimal reproducer — a v2rayN-style base64-encoded VMess share link that declares
net: h2with a disguisehost:上面的 base64 负载解码后是:
The base64 payload decodes to:
{"v":"2","ps":"demo-h2-host-lost","add":"server.example.com","port":"443","id":"b831381d-6324-4d53-ad4f-8cda48b30811","aid":"0","scy":"auto","net":"h2","type":"none","host":"cdn.example.com","path":"/grpc","tls":"tls"}导入后,生成的 proxy
h2-opts.host为空——订阅里指定的伪装域名cdn.example.com被静默丢弃。After import, the resulting proxy has
h2-opts.hostempty — the intended disguise hostcdn.example.comis silently lost.Mihomo log
不适用——bug 是静默的,没有 error、warning 或 debug 日志。对上述分享链接运行
ConvertsV2Ray,实际得到的h2-optsmap 是:N/A — the bug is silent. No error, no warning, no debug line. Running
ConvertsV2Rayon the link above produces anh2-optsmap that looks like:h2-opts.host完全缺失;伪装 host 被写进了HTTP2Options根本不存在的headers键里,被解码器静默丢弃。h2-opts.hostis missing entirely; the disguise host lives inside a key (headers) thatHTTP2Optionsdoes not have, so the decoder silently discards it.Description
common/convert/converter.go:333-343的case "h2":分支——也就是老式 v2rayN 风格 base64-JSON VMess 分享链接的解析路径——把伪装host写到了错误的 map key 上。结果是:每一个使用 HTTP/2 transport 的 VMess 订阅节点在导入时都会静默丢掉自己的伪装 host。The
case "h2":branch incommon/convert/converter.go:333-343— the legacy v2rayN-style base64-JSON VMess share-link parser — writes the disguisehostinto the wrong map key. As a result, every VMess subscription node that uses HTTP/2 transport silently loses its disguise host during import.当前代码 / Current code:
下游 schema / Downstream schema (
adapter/outbound/vmess.go:80-83):没有
Headers字段。h2Opts["headers"]在common/structure解码时是一个未知 key(WeaklyTypedInput: true、未启用ErrorUnused),被静默丢弃,里面的host值也随之丢失。There is no
Headersfield.h2Opts["headers"]is an unknown key duringcommon/structuredecoding (WeaklyTypedInput: true,ErrorUnusednot enabled) and is silently discarded; thehostvalue placed inside it is lost with it.正确的 key 应该是
h2Opts["host"] = []string{host}。The correct key is
h2Opts["host"] = []string{host}.与 #2555 / #2737 的关系 / Relation to #2555 / #2737
common/convert/v.go(新式 VMess AEAD / VLESS URL 解析器)里有一个平行 bug,已由 #2555 报告、#2737 修复。两者同源——都是从相邻的case "http":(HTTP/1.1 camouflage)分支 copy-paste 过来的——但运行在不同的订阅格式上:The parallel bug in
common/convert/v.go(the modern VMess AEAD / VLESS URL parser) was reported as #2555 and fixed in #2737. Both originate from the same copy-paste pattern (cloned from the adjacentcase "http":HTTP/1.1 camouflage block), but each runs on a different subscription format:v.go handleVShareLink—— 新式vmess://...?type=http&.../vless://...?type=http&...URLconverter.go case "h2":—— 老式 v2rayN 风格 base64 编码 JSON 的vmess://<base64>v.go handleVShareLink— modernvmess://...?type=http&.../vless://...?type=http&...URLsconverter.go case "h2":— legacy v2rayN-style base64-encoded JSONvmess://<base64>#2737 没覆盖老式路径,因为入口和编码都不同。本 issue 跟踪老式路径的修复。
#2737 did not cover the legacy path because the entry point and encoding are different. This issue tracks the legacy path fix.
复现步骤 / Reproduction
从 Alpha HEAD 构建 mihomo / Build mihomo from Alpha HEAD.
导入任何 v2rayN 风格、JSON 里
"net": "h2"(或"net": "http",converter.go 会把它 remap 成"h2")且"host"非空的订阅。上面给出的最小 config 即可。Import any v2rayN-style subscription whose JSON has
"net": "h2"(or"net": "http", which converter.go remaps to"h2") and a non-empty"host". The minimal config above works.通过
/proxiesAPI 或 config dump 查看加载后的 proxy——h2-opts.host为空,订阅里指定的伪装 host 消失了。Inspect the loaded proxy via the
/proxiesAPI or a config dump —h2-opts.hostis empty; the disguise host from the subscription is gone.建议修复 / Proposed fix
直接照搬 #2737 对
v.go平行分支的修复思路:Direct mirror of the fix applied by #2737 to the parallel
v.gobranch:我会准备 PR。
I'll prepare a PR.