From 03e89683dd2ae7c1ef3e551556e7d97ee9e0ee52 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 21 Jun 2026 19:27:37 +0200 Subject: [PATCH] fix(tls): ping the inbound's own port for remote cert pinning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pin-from-remote button passed only the SNI to 'xray tls ping', which defaults to :443 — so it never reached a self-hosted inbound on another port and failed with a vague 'no certificate hash found'. Append the inbound's port when the SNI carries none, and surface the underlying ping failure (dial refused, timeout) in the error. --- frontend/src/pages/inbounds/form/useSecurityActions.ts | 7 ++++++- internal/web/service/server.go | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/inbounds/form/useSecurityActions.ts b/frontend/src/pages/inbounds/form/useSecurityActions.ts index 53428093e..980fe6bc8 100644 --- a/frontend/src/pages/inbounds/form/useSecurityActions.ts +++ b/frontend/src/pages/inbounds/form/useSecurityActions.ts @@ -143,9 +143,14 @@ export function useSecurityActions({ form, setSaving, messageApi, nodeId }: UseS messageApi.warning(t('pages.inbounds.form.pinFromRemoteNoSni')); return; } + // `xray tls ping` defaults to :443, but a self-hosted inbound rarely + // listens there. Append the inbound's own port (unless the SNI already + // carries one) so the ping reaches the actual TLS endpoint. + const port = form.getFieldValue('port') as number | undefined; + const target = /:\d+$/.test(server) || !port ? server : `${server}:${port}`; setSaving(true); try { - const msg = await HttpUtil.post('/panel/api/server/getRemoteCertHash', { server }); + const msg = await HttpUtil.post('/panel/api/server/getRemoteCertHash', { server: target }); if (!msg?.success) { messageApi.warning(msg?.msg || t('pages.inbounds.form.pinFromRemoteFailed')); return; diff --git a/internal/web/service/server.go b/internal/web/service/server.go index b1a05393d..878ab985f 100644 --- a/internal/web/service/server.go +++ b/internal/web/service/server.go @@ -1896,6 +1896,15 @@ func (s *ServerService) GetRemoteCertHash(server string) ([]string, error) { } } if len(leaves) == 0 { + // Surface why the ping produced no cert (dial refused, timeout, …) + // instead of the bare "not found" — the inbound is usually just not + // listening for TLS on the pinged port. + for _, line := range strings.Split(out.String(), "\n") { + line = strings.TrimSpace(line) + if strings.Contains(line, "Failed") || strings.Contains(line, "error") { + return nil, common.NewError("no certificate hash for ", server, ": ", line) + } + } return nil, common.NewError("no certificate hash found for ", server) } return leaves, nil