From 5eec17848343c59beee3dbc08beadb3c0ac25c99 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Fri, 12 Jun 2026 17:58:45 +0200 Subject: [PATCH] feat(mtproto): route Telegram egress through Xray routing rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a per-inbound "Route through Xray" toggle (off by default) plus an optional outbound picker on MTProto inbounds. mtg only supports a SOCKS5 upstream, so when enabled the panel injects a loopback SOCKS bridge into the generated Xray config — tagged with the inbound's own tag — and mtg dials Telegram through it via a [network] proxies upstream. The router then governs Telegram egress: matchable in the Routing tab, or forced to a chosen outbound/balancer via the picker. - mtproto: Instance carries RouteThroughXray + XrayRoutePort (in the fingerprint); InstanceFromInbound parses them; renderConfig emits the socks5 [network] upstream; freeLocalPort exported as FreeLocalPort. - xray.go: injectMtprotoEgress appends the loopback SOCKS bridge and prepends an optional inboundTag->outbound/balancer rule, hot-appliable like injectPanelEgress. - inbound.go: backend-owned egress port persisted in settings, allocated once and carried across edits (stored value wins); stripped with the inert outboundTag when routing is off; allocation failure fails the save; routed add/update/del force a config regen. - mtproto_job: skip folding mtg metrics for routed inbounds (the bridge, carrying the inbound tag, is metered by xray_traffic_job) to avoid double-counting. - frontend: toggle + outbound/balancer Select (useOutboundTags) on the MTProto form; i18n keys for all locales. --- frontend/src/api/queries/useOutboundTags.ts | 33 +++++ frontend/src/hooks/useXraySetting.ts | 2 +- .../pages/inbounds/form/protocols/mtproto.tsx | 25 ++++ .../src/schemas/protocols/inbound/mtproto.ts | 7 + internal/mtproto/manager.go | 25 +++- internal/mtproto/manager_test.go | 34 ++++- internal/web/job/mtproto_job.go | 13 ++ internal/web/service/inbound.go | 129 ++++++++++++++++++ internal/web/service/inbound_mtproto_test.go | 94 +++++++++++++ internal/web/service/xray.go | 82 +++++++++++ .../web/service/xray_config_inject_test.go | 97 +++++++++++++ internal/web/translation/ar-EG.json | 5 + internal/web/translation/en-US.json | 5 + internal/web/translation/es-ES.json | 5 + internal/web/translation/fa-IR.json | 5 + internal/web/translation/id-ID.json | 5 + internal/web/translation/ja-JP.json | 5 + internal/web/translation/pt-BR.json | 5 + internal/web/translation/ru-RU.json | 5 + internal/web/translation/tr-TR.json | 5 + internal/web/translation/uk-UA.json | 5 + internal/web/translation/vi-VN.json | 5 + internal/web/translation/zh-CN.json | 5 + internal/web/translation/zh-TW.json | 5 + 24 files changed, 602 insertions(+), 4 deletions(-) create mode 100644 frontend/src/api/queries/useOutboundTags.ts create mode 100644 internal/web/service/inbound_mtproto_test.go diff --git a/frontend/src/api/queries/useOutboundTags.ts b/frontend/src/api/queries/useOutboundTags.ts new file mode 100644 index 000000000..102dea7f8 --- /dev/null +++ b/frontend/src/api/queries/useOutboundTags.ts @@ -0,0 +1,33 @@ +import { useQuery } from '@tanstack/react-query'; + +import { keys } from '@/api/queryKeys'; +import { fetchXrayConfig } from '@/hooks/useXraySetting'; + +// Available outbound (and balancer-eligible) tags the user can route an mtproto +// inbound's Telegram traffic to. Shares the cached xray config query so opening +// the inbound form costs no extra request when the Xray page was already +// visited; `select` derives just the tag list without disturbing other readers. +export function useOutboundTags() { + return useQuery({ + queryKey: keys.xray.config(), + queryFn: fetchXrayConfig, + staleTime: Infinity, + select: (data): string[] => { + const tags = new Set(); + for (const o of data?.xraySetting?.outbounds ?? []) { + const tag = (o as { tag?: string } | null)?.tag; + if (tag) tags.add(tag); + } + for (const t of data?.subscriptionOutboundTags ?? []) { + if (t) tags.add(t); + } + // Balancers are valid routing targets too — injectMtprotoEgress emits a + // balancerTag rule when the chosen tag names a balancer. + const balancers = (data?.xraySetting?.routing as { balancers?: Array<{ tag?: string }> } | undefined)?.balancers; + for (const b of balancers ?? []) { + if (b?.tag) tags.add(b.tag); + } + return [...tags]; + }, + }); +} diff --git a/frontend/src/hooks/useXraySetting.ts b/frontend/src/hooks/useXraySetting.ts index 8a024d5aa..9542b86cb 100644 --- a/frontend/src/hooks/useXraySetting.ts +++ b/frontend/src/hooks/useXraySetting.ts @@ -81,7 +81,7 @@ export interface UseXraySettingResult { type XrayConfigPayload = z.infer; -async function fetchXrayConfig(): Promise { +export async function fetchXrayConfig(): Promise { const msg = await HttpUtil.post('/panel/api/xray/', undefined, { silent: true }); if (!msg?.success) throw new Error(msg?.msg || 'Failed to load xray config'); if (typeof msg.obj !== 'string') throw new Error('Malformed xray config response: expected string'); diff --git a/frontend/src/pages/inbounds/form/protocols/mtproto.tsx b/frontend/src/pages/inbounds/form/protocols/mtproto.tsx index 0a435e7e5..378e796ac 100644 --- a/frontend/src/pages/inbounds/form/protocols/mtproto.tsx +++ b/frontend/src/pages/inbounds/form/protocols/mtproto.tsx @@ -3,10 +3,13 @@ import { Button, Form, Input, InputNumber, Select, Space, Switch } from 'antd'; import { ReloadOutlined } from '@ant-design/icons'; import { generateMtprotoSecret, mtprotoSecretForDomain } from '@/lib/xray/inbound-defaults'; +import { useOutboundTags } from '@/api/queries/useOutboundTags'; export default function MtprotoFields() { const { t } = useTranslation(); const form = Form.useFormInstance(); + const routeThroughXray = Form.useWatch(['settings', 'routeThroughXray'], form) as boolean | undefined; + const { data: outboundTags } = useOutboundTags(); return ( <> @@ -71,6 +74,28 @@ export default function MtprotoFields() { + + + + {routeThroughXray && ( + +