From 337ecc44c346b59a2d782db66137aab8b2f48c93 Mon Sep 17 00:00:00 2001 From: Maksim Alekseev Date: Fri, 22 May 2026 09:39:12 +0300 Subject: [PATCH] :sparkles: Add custom SNI for proxy --- frontend/src/models/inbound.js | 6 +++-- .../src/pages/inbounds/InboundFormModal.vue | 8 ++++--- sub/subService.go | 22 ++++++++++++++----- sub/subService_test.go | 16 ++++++++++++-- 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/frontend/src/models/inbound.js b/frontend/src/models/inbound.js index da85732b..d98dd8ba 100644 --- a/frontend/src/models/inbound.js +++ b/frontend/src/models/inbound.js @@ -1703,7 +1703,8 @@ export class Inbound extends XrayCommonClass { static applyExternalProxyTLSParams(externalProxy, params, security) { if (!externalProxy || security !== 'tls') return; - if (externalProxy.dest?.length > 0) params.set("sni", externalProxy.dest); + const sni = externalProxy.sni?.length > 0 ? externalProxy.sni : externalProxy.dest; + if (sni?.length > 0) params.set("sni", sni); if (externalProxy.fingerprint?.length > 0) params.set("fp", externalProxy.fingerprint); const alpn = Inbound.externalProxyAlpn(externalProxy.alpn); if (alpn.length > 0) params.set("alpn", alpn); @@ -1711,7 +1712,8 @@ export class Inbound extends XrayCommonClass { static applyExternalProxyTLSObj(externalProxy, obj, security) { if (!externalProxy || !obj || security !== 'tls') return; - if (externalProxy.dest?.length > 0) obj.sni = externalProxy.dest; + const sni = externalProxy.sni?.length > 0 ? externalProxy.sni : externalProxy.dest; + if (sni?.length > 0) obj.sni = sni; if (externalProxy.fingerprint?.length > 0) obj.fp = externalProxy.fingerprint; const alpn = Inbound.externalProxyAlpn(externalProxy.alpn); if (alpn.length > 0) obj.alpn = alpn; diff --git a/frontend/src/pages/inbounds/InboundFormModal.vue b/frontend/src/pages/inbounds/InboundFormModal.vue index 65330586..2961c306 100644 --- a/frontend/src/pages/inbounds/InboundFormModal.vue +++ b/frontend/src/pages/inbounds/InboundFormModal.vue @@ -101,6 +101,7 @@ const externalProxy = computed({ dest: window.location.hostname, port: inbound.value.port, remark: '', + sni: '', fingerprint: '', alpn: [], }]; @@ -1685,7 +1686,7 @@ watch(() => inbound.value?.protocol, () => stampAdvancedTextFor('stream')); + @click="inbound.stream.externalProxy.push({ forceTls: 'same', dest: '', port: 443, remark: '', sni: '', fingerprint: '', alpn: [] })"> @@ -1712,11 +1713,12 @@ watch(() => inbound.value?.protocol, () => stampAdvancedTextFor('stream')); - + + Default {{ fp }} - + {{ alpn }} diff --git a/sub/subService.go b/sub/subService.go index 41ca3c2a..b841419f 100644 --- a/sub/subService.go +++ b/sub/subService.go @@ -867,8 +867,8 @@ func applyExternalProxyTLSObj(ep map[string]any, obj map[string]any, security st if security != "tls" { return } - if dest, ok := ep["dest"].(string); ok && dest != "" { - obj["sni"] = dest + if sni, ok := externalProxySNI(ep); ok { + obj["sni"] = sni } if fp, ok := ep["fingerprint"].(string); ok && fp != "" { obj["fp"] = fp @@ -882,8 +882,8 @@ func applyExternalProxyTLSParams(ep map[string]any, params map[string]string, se if security != "tls" { return } - if dest, ok := ep["dest"].(string); ok && dest != "" { - params["sni"] = dest + if sni, ok := externalProxySNI(ep); ok { + params["sni"] = sni } if fp, ok := ep["fingerprint"].(string); ok && fp != "" { params["fp"] = fp @@ -902,8 +902,8 @@ func applyExternalProxyTLSToStream(ep map[string]any, stream map[string]any, sec tlsSettings = map[string]any{} stream["tlsSettings"] = tlsSettings } - if dest, ok := ep["dest"].(string); ok && dest != "" { - tlsSettings["serverName"] = dest + if sni, ok := externalProxySNI(ep); ok { + tlsSettings["serverName"] = sni } if fp, ok := ep["fingerprint"].(string); ok && fp != "" { tlsSettings["fingerprint"] = fp @@ -919,6 +919,16 @@ func applyExternalProxyTLSToStream(ep map[string]any, stream map[string]any, sec } } +func externalProxySNI(ep map[string]any) (string, bool) { + if sni, ok := ep["sni"].(string); ok && sni != "" { + return sni, true + } + if dest, ok := ep["dest"].(string); ok && dest != "" { + return dest, true + } + return "", false +} + func externalProxyALPN(value any) (string, bool) { switch v := value.(type) { case string: diff --git a/sub/subService_test.go b/sub/subService_test.go index b8a512e5..3a3451af 100644 --- a/sub/subService_test.go +++ b/sub/subService_test.go @@ -449,14 +449,15 @@ func TestApplyExternalProxyTLSParams_UsesProxyDomainAndOverrides(t *testing.T) { } ep := map[string]any{ "dest": "proxy.example.com", + "sni": "tls.example.com", "fingerprint": "chrome", "alpn": []any{"h3", "h2"}, } applyExternalProxyTLSParams(ep, params, "tls") - if params["sni"] != "proxy.example.com" { - t.Fatalf("sni = %q, want proxy.example.com", params["sni"]) + if params["sni"] != "tls.example.com" { + t.Fatalf("sni = %q, want tls.example.com", params["sni"]) } if params["fp"] != "chrome" { t.Fatalf("fp = %q, want chrome", params["fp"]) @@ -466,6 +467,17 @@ func TestApplyExternalProxyTLSParams_UsesProxyDomainAndOverrides(t *testing.T) { } } +func TestApplyExternalProxyTLSParams_FallsBackToDestSNI(t *testing.T) { + params := map[string]string{"security": "tls"} + ep := map[string]any{"dest": "proxy.example.com"} + + applyExternalProxyTLSParams(ep, params, "tls") + + if params["sni"] != "proxy.example.com" { + t.Fatalf("sni = %q, want proxy.example.com", params["sni"]) + } +} + func TestApplyExternalProxyTLSParams_DoesNotApplyForNone(t *testing.T) { params := map[string]string{ "security": "none",