mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-21 10:27:14 +00:00
feat(mtproto): route Telegram egress through Xray routing rules
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.
This commit is contained in:
@@ -40,7 +40,8 @@ func TestInstanceFromInbound(t *testing.T) {
|
||||
Protocol: model.MTProto,
|
||||
Settings: `{"fakeTlsDomain":"example.com","secret":"",` +
|
||||
`"debug":true,"proxyProtocolListener":true,"preferIp":"prefer-ipv4",` +
|
||||
`"domainFronting":{"ip":"127.0.0.1","port":9443,"proxyProtocol":true}}`,
|
||||
`"domainFronting":{"ip":"127.0.0.1","port":9443,"proxyProtocol":true},` +
|
||||
`"routeThroughXray":true,"routeXrayPort":50000}`,
|
||||
}
|
||||
inst, ok := InstanceFromInbound(ib)
|
||||
if !ok {
|
||||
@@ -58,6 +59,9 @@ func TestInstanceFromInbound(t *testing.T) {
|
||||
if inst.FrontingIP != "127.0.0.1" || inst.FrontingPort != 9443 || !inst.FrontingProxyProtocol {
|
||||
t.Fatalf("domain-fronting not parsed: %+v", inst)
|
||||
}
|
||||
if !inst.RouteThroughXray || inst.XrayRoutePort != 50000 {
|
||||
t.Fatalf("xray routing not parsed: %+v", inst)
|
||||
}
|
||||
|
||||
if _, ok := InstanceFromInbound(&model.Inbound{Protocol: model.VLESS}); ok {
|
||||
t.Fatal("non-mtproto inbound should not produce an instance")
|
||||
@@ -108,6 +112,32 @@ func TestRenderConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderConfigXrayEgress(t *testing.T) {
|
||||
// Routing through Xray emits a [network] proxies upstream pointing at the
|
||||
// loopback SOCKS bridge, before the prometheus block.
|
||||
routed := renderConfig(Instance{
|
||||
Secret: "ee22", Listen: "0.0.0.0", Port: 443,
|
||||
RouteThroughXray: true, XrayRoutePort: 50000,
|
||||
}, 7000)
|
||||
if !strings.Contains(routed, "[network]") ||
|
||||
!strings.Contains(routed, `proxies = ["socks5://127.0.0.1:50000"]`) {
|
||||
t.Fatalf("routed config must emit the SOCKS upstream:\n%s", routed)
|
||||
}
|
||||
if strings.Index(routed, "[network]") > strings.Index(routed, "[stats.prometheus]") {
|
||||
t.Fatalf("[network] must precede [stats.prometheus]:\n%s", routed)
|
||||
}
|
||||
|
||||
// Without the flag (or without a port) the section is omitted.
|
||||
for _, inst := range []Instance{
|
||||
{Secret: "ee", Listen: "0.0.0.0", Port: 443},
|
||||
{Secret: "ee", Listen: "0.0.0.0", Port: 443, RouteThroughXray: true},
|
||||
} {
|
||||
if got := renderConfig(inst, 7000); strings.Contains(got, "[network]") {
|
||||
t.Fatalf("unrouted config must omit [network]:\n%s", got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFingerprintReactsToOptions(t *testing.T) {
|
||||
base := Instance{Secret: "ee", Listen: "0.0.0.0", Port: 443}
|
||||
for name, mutate := range map[string]func(*Instance){
|
||||
@@ -117,6 +147,8 @@ func TestFingerprintReactsToOptions(t *testing.T) {
|
||||
"frontingIP": func(i *Instance) { i.FrontingIP = "127.0.0.1" },
|
||||
"frontingPort": func(i *Instance) { i.FrontingPort = 9443 },
|
||||
"frontingProxy": func(i *Instance) { i.FrontingProxyProtocol = true },
|
||||
"routeXray": func(i *Instance) { i.RouteThroughXray = true },
|
||||
"routeXrayPort": func(i *Instance) { i.XrayRoutePort = 50000 },
|
||||
} {
|
||||
changed := base
|
||||
mutate(&changed)
|
||||
|
||||
Reference in New Issue
Block a user