feat(tls,reality): port xray TLS/REALITY fields, cert-hash helpers, fallback UX

TLS: add verifyPeerCertByName (vcn) to inbound settings + emit in both share-link generators (frontend + Go sub) and outbound parser; the allowInsecure replacement xray removed after 2026-06-01. Add server-side curvePreferences, masterKeyLog, echSockopt (passthrough + form) at tlsSettings top-level so they survive the panel-only settings strip.

REALITY: add limitFallbackUpload/Download (afterBytes/bytesPerSec/burstBytesPerSec) with per-field tooltips, plus masterKeyLog. Verified field names/semantics against pinned xray v1.260327.1 (bytesPerSec=0 disables).

Hosts: fix verify_peer_cert_by_name column bool->string (xray expects comma-separated names) with an idempotent, history-gate-free migration (SQLite typeof blank; Postgres ALTER once); emit vcn for hosts/external proxies.

Server: add getCertHash (local cert DER SHA-256) and getRemoteCertHash (xray tls ping) endpoints + api-docs; wire pinned-cert field buttons. Drop the meaningless random-hash button.

Xray UI: metrics endpoint (listen/tag) config in Basics; import/export for routing rules and outbounds.

Fallbacks card: compact empty state, header-aligned actions, responsive labeled grid rows.

i18n: add all new keys to every locale; drop unused generateRandomPin.
This commit is contained in:
MHSanaei
2026-06-21 15:51:50 +02:00
parent 315ecc2588
commit 7c8889466b
48 changed files with 1316 additions and 173 deletions
+3
View File
@@ -80,6 +80,9 @@ func hostToExternalProxyMap(h *model.Host, defaultDest string, defaultPort int)
if h.EchConfigList != "" {
ep["echConfigList"] = h.EchConfigList
}
if h.VerifyPeerCertByName != "" {
ep["verifyPeerCertByName"] = h.VerifyPeerCertByName
}
if h.AllowInsecure {
ep["allowInsecure"] = true
}
+3
View File
@@ -309,6 +309,9 @@ func (s *SubJsonService) tlsData(tData map[string]any) map[string]any {
if ech, ok := tlsClientSettings["echConfigList"].(string); ok && ech != "" {
tlsData["echConfigList"] = ech
}
if vcn, ok := verifyPeerCertByNameValue(tlsClientSettings); ok {
tlsData["verifyPeerCertByName"] = vcn
}
// xray-core now parses pinnedPeerCertSha256 as a comma-separated string, not
// an array; emit the joined form so v2ray clients can import the config (#5401).
if pins, ok := pinnedSha256List(tlsClientSettings); ok {
+42
View File
@@ -813,6 +813,9 @@ func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) strin
params["ech"] = ech
}
}
if vcn, ok := verifyPeerCertByNameValue(tlsSettings); ok {
params["vcn"] = vcn
}
if pins, ok := pinnedSha256List(tlsSettings); ok {
for i, p := range pins {
pins[i] = hysteriaPinHex(p)
@@ -1120,6 +1123,9 @@ func applyShareTLSParams(stream map[string]any, params map[string]string) {
params["ech"] = ech
}
}
if vcn, ok := verifyPeerCertByNameValue(tlsSettings); ok {
params["vcn"] = vcn
}
if pins, ok := pinnedSha256List(tlsSettings); ok {
params["pcs"] = strings.Join(pins, ",")
}
@@ -1150,12 +1156,34 @@ func applyVmessTLSParams(stream map[string]any, obj map[string]any) {
obj["ech"] = ech
}
}
if vcn, ok := verifyPeerCertByNameValue(tlsSettings); ok {
obj["vcn"] = vcn
}
if pins, ok := pinnedSha256List(tlsSettings); ok {
obj["pcs"] = strings.Join(pins, ",")
}
}
}
// verifyPeerCertByNameValue extracts tlsSettings.settings.verifyPeerCertByName
// (the v2rayN `vcn` param) as a trimmed string. Like pinnedPeerCertSha256 it is
// panel-only and flows into share links so clients verify the server
// certificate by this name — the replacement for the removed allowInsecure.
func verifyPeerCertByNameValue(tlsClientSettings any) (string, bool) {
raw, ok := searchKey(tlsClientSettings, "verifyPeerCertByName")
if !ok {
return "", false
}
s, ok := raw.(string)
if !ok {
return "", false
}
if s = strings.TrimSpace(s); s == "" {
return "", false
}
return s, true
}
// pinnedSha256List extracts tlsSettings.settings.pinnedPeerCertSha256 as a
// []string. The field is panel-only (stripped before the run-config reaches
// xray-core via internal/web/service/xray.go) but flows into share links so clients
@@ -1274,6 +1302,9 @@ func applyExternalProxyTLSObj(ep map[string]any, obj map[string]any, security st
if pins, ok := externalProxyPins(ep["pinnedPeerCertSha256"]); ok {
obj["pcs"] = joinAnyStrings(pins)
}
if vcn, ok := ep["verifyPeerCertByName"].(string); ok && vcn != "" {
obj["vcn"] = vcn
}
if ech, ok := ep["echConfigList"].(string); ok && ech != "" {
obj["ech"] = ech
}
@@ -1295,6 +1326,9 @@ func applyExternalProxyTLSParams(ep map[string]any, params map[string]string, se
if pins, ok := externalProxyPins(ep["pinnedPeerCertSha256"]); ok {
params["pcs"] = joinAnyStrings(pins)
}
if vcn, ok := ep["verifyPeerCertByName"].(string); ok && vcn != "" {
params["vcn"] = vcn
}
if ech, ok := ep["echConfigList"].(string); ok && ech != "" {
params["ech"] = ech
}
@@ -1378,6 +1412,14 @@ func applyExternalProxyTLSToStream(ep map[string]any, stream map[string]any, sec
}
settings["echConfigList"] = ech
}
if vcn, ok := ep["verifyPeerCertByName"].(string); ok && vcn != "" {
settings, _ := tlsSettings["settings"].(map[string]any)
if settings == nil {
settings = map[string]any{}
tlsSettings["settings"] = settings
}
settings["verifyPeerCertByName"] = vcn
}
if ai, ok := ep["allowInsecure"].(bool); ok && ai {
settings, _ := tlsSettings["settings"].(map[string]any)
if settings == nil {