1
0
mirror of https://github.com/MetaCubeX/mihomo.git synced 2026-10-10 04:03:11 +08:00

chore: converter map hysteria2 port hopping and realm URI (#2947)

This commit is contained in:
Peter Solomon
2026-07-08 02:33:49 +03:00
committed by GitHub
parent f62ebd0d04
commit cbd11db1e1
3 changed files with 97 additions and 5 deletions
+17 -5
View File
@@ -69,8 +69,10 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
proxies = append(proxies, hysteria)
case "hysteria2", "hy2":
urlHysteria2, err := url.Parse(line)
case "hysteria2", "hy2", "hysteria2+realm", "hy2+realm":
realmMode := strings.HasSuffix(scheme, "+realm")
hopLine, ports := splitHysteria2Ports(line)
urlHysteria2, err := url.Parse(hopLine)
if err != nil {
continue
}
@@ -87,6 +89,9 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
} else {
hysteria2["port"] = "443"
}
if ports != "" {
hysteria2["ports"] = ports
}
hysteria2["obfs"] = query.Get("obfs")
hysteria2["obfs-password"] = query.Get("obfs-password")
hysteria2["sni"] = query.Get("sni")
@@ -94,12 +99,19 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
if alpn := query.Get("alpn"); alpn != "" {
hysteria2["alpn"] = strings.Split(alpn, ",")
}
if auth := urlHysteria2.User.String(); auth != "" {
hysteria2["password"] = auth
}
hysteria2["fingerprint"] = query.Get("pinSHA256")
hysteria2["down"] = query.Get("down")
hysteria2["up"] = query.Get("up")
if realmMode {
token, _ := url.PathUnescape(urlHysteria2.User.String())
realmID, _ := url.PathUnescape(strings.TrimPrefix(urlHysteria2.EscapedPath(), "/"))
hysteria2["realm-opts"] = buildRealmOpts("https://"+urlHysteria2.Host, token, realmID, query["stun"])
if auth := query.Get("auth"); auth != "" {
hysteria2["password"] = auth
}
} else if auth := urlHysteria2.User.String(); auth != "" {
hysteria2["password"] = auth
}
proxies = append(proxies, hysteria2)
+31
View File
@@ -40,6 +40,37 @@ func TestConvertsV2Ray_normal(t *testing.T) {
assert.NoError(t, err)
}
func TestConvertsV2Ray_hysteria2PortHopping(t *testing.T) {
uri := "hysteria2://letmein@example.com:443,5000-6000/?sni=example.com#hop"
proxies, err := ConvertsV2Ray([]byte(uri))
assert.Nil(t, err)
assert.Equal(t, "example.com", proxies[0]["server"])
assert.Equal(t, "443", proxies[0]["port"])
assert.Equal(t, "443,5000-6000", proxies[0]["ports"])
_, err = adapter.ParseProxy(proxies[0])
assert.NoError(t, err)
}
func TestConvertsV2Ray_hysteria2RealmScheme(t *testing.T) {
uri := "hysteria2+realm://tok3n@rendezvous.example.com:8443/rid42?auth=letmein&stun=stun1:3478&stun=stun2:3478&sni=example.com#realm"
proxies, err := ConvertsV2Ray([]byte(uri))
assert.Nil(t, err)
p := proxies[0]
assert.Equal(t, "hysteria2", p["type"])
assert.Equal(t, "letmein", p["password"])
assert.Equal(t, map[string]any{
"enable": true,
"server-url": "https://rendezvous.example.com:8443",
"token": "tok3n",
"realm-id": "rid42",
"stun-servers": []string{"stun1:3478", "stun2:3478"},
}, p["realm-opts"])
_, err = adapter.ParseProxy(p)
assert.NoError(t, err)
}
func TestConvertsV2RayMieru(t *testing.T) {
mierusTest := "mierus://user:pass@1.2.3.4?handshake-mode=HANDSHAKE_NO_WAIT&mtu=1400&multiplexing=MULTIPLEXING_HIGH&port=6666&port=9998-9999&port=6489&port=4896&profile=default&protocol=TCP&protocol=TCP&protocol=UDP&protocol=UDP&traffic-pattern=CCoQARoECAEQCiIYCAMQASoIMDAwMTAyMDMqCDA0MDUwNjA3"
+49
View File
@@ -363,3 +363,52 @@ func parseXHTTPExtra(extra map[string]any, opts map[string]any) {
}
}
}
func buildRealmOpts(serverURL, token, realmID string, stunServers []string) map[string]any {
opts := map[string]any{"enable": true, "server-url": serverURL}
if token != "" {
opts["token"] = token
}
if realmID != "" {
opts["realm-id"] = realmID
}
if len(stunServers) > 0 {
opts["stun-servers"] = stunServers
}
return opts
}
func splitHysteria2Ports(line string) (string, string) {
i := strings.Index(line, "://")
if i < 0 {
return line, ""
}
head, rest := line[:i+3], line[i+3:]
auth, tail := rest, ""
if j := strings.IndexAny(rest, "/?#"); j >= 0 {
auth, tail = rest[:j], rest[j:]
}
userinfo := ""
if at := strings.LastIndex(auth, "@"); at >= 0 {
userinfo, auth = auth[:at+1], auth[at+1:]
}
if strings.Contains(auth, "]") {
return line, ""
}
colon := strings.LastIndex(auth, ":")
if colon < 0 {
return line, ""
}
host, ports := auth[:colon], auth[colon+1:]
if !strings.ContainsAny(ports, ",-") {
return line, ""
}
first := ports
if k := strings.IndexAny(ports, ",-"); k >= 0 {
first = ports[:k]
}
if first == "" {
return line, ""
}
return head + userinfo + host + ":" + first + tail, ports
}