mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-12 15:46:06 +00:00
feat(mtproto): add MTProto (FakeTLS) protocol via managed mtg sidecar (#5076)
* feat(mtproto): add MTProto (FakeTLS) protocol via managed mtg sidecar Xray-core has no mtproto proxy, so mtproto inbounds run as standalone mtg (9seconds/mtg) sidecar processes managed by the panel — one per inbound — and are excluded from the generated Xray config entirely. - model: MTProto protocol constant, validator, and FakeTLS secret helpers (GenerateFakeTLSSecret/HealMtprotoSecret) - mtproto package: per-inbound mtg process manager with reconcile, graceful stop, and best-effort Prometheus traffic scraping - runtime: delegate mtproto inbounds to the mtg manager instead of the Xray gRPC API; skip mtproto when building the Xray config - web: boot reconcile + StopAll wiring, periodic reconcile/traffic job, port-conflict transport, secret healing on inbound add/update - sub: tg:// proxy share-link generation - frontend: protocol option, Zod schema, Protocol tab (FakeTLS domain + regenerable secret), info-modal link, and i18n - provisioning: fetch mtg v2.2.8 in install.sh, DockerInit.sh, and the Linux + Windows release workflows * fix * fix * fix: address Copilot review comments on mtproto PR - web/web.go: create NewMtprotoJob once and reuse for cron + initial run - mtproto/manager.go: StopAll cleans up per-inbound config files on shutdown - mtproto/manager.go: CollectTraffic releases mutex before HTTP scrapes to avoid blocking Ensure/Reconcile/Remove during network I/O - database/model/model.go: panic on crypto/rand failure in mtprotoRandomMiddle instead of silently producing a weak all-zero secret - install.sh: fix chmod to handle renamed bin/mtg-linux-arm on armv5/v6/v7
This commit is contained in:
@@ -3,6 +3,7 @@ import { RandomUtil, Wireguard } from '@/utils';
|
||||
import type { HttpInboundSettings } from '@/schemas/protocols/inbound/http';
|
||||
import type { HysteriaClient, HysteriaInboundSettings } from '@/schemas/protocols/inbound/hysteria';
|
||||
import type { MixedInboundSettings } from '@/schemas/protocols/inbound/mixed';
|
||||
import type { MtprotoInboundSettings } from '@/schemas/protocols/inbound/mtproto';
|
||||
import type { ShadowsocksClient, ShadowsocksInboundSettings } from '@/schemas/protocols/inbound/shadowsocks';
|
||||
import type { TrojanClient, TrojanInboundSettings } from '@/schemas/protocols/inbound/trojan';
|
||||
import type { TunInboundSettings } from '@/schemas/protocols/inbound/tun';
|
||||
@@ -200,6 +201,43 @@ export function createDefaultMixedInboundSettings(): MixedInboundSettings {
|
||||
};
|
||||
}
|
||||
|
||||
function domainToHex(domain: string): string {
|
||||
return Array.from(new TextEncoder().encode(domain))
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
}
|
||||
|
||||
// generateMtprotoSecret builds an "ee" FakeTLS secret: the marker, 16 random
|
||||
// bytes (32 hex chars), then the domain encoded as hex. Mirrors the Go
|
||||
// model.GenerateFakeTLSSecret; the backend re-derives it on save so this is
|
||||
// only for immediate display in the form.
|
||||
export function generateMtprotoSecret(domain: string): string {
|
||||
return `ee${RandomUtil.randomSeq(32, { type: 'hex' })}${domainToHex(domain)}`;
|
||||
}
|
||||
|
||||
// mtprotoSecretForDomain rewrites only the domain suffix of an existing secret,
|
||||
// preserving its 16-byte random middle when valid (generating one otherwise).
|
||||
// Mirrors the Go model.HealMtprotoSecret so editing the FakeTLS domain doesn't
|
||||
// needlessly rotate the secret's identity.
|
||||
export function mtprotoSecretForDomain(currentSecret: string, domain: string): string {
|
||||
let body = currentSecret;
|
||||
if (body.startsWith('ee') || body.startsWith('dd')) {
|
||||
body = body.slice(2);
|
||||
}
|
||||
const middle = /^[0-9a-f]{32}/i.test(body)
|
||||
? body.slice(0, 32)
|
||||
: RandomUtil.randomSeq(32, { type: 'hex' });
|
||||
return `ee${middle}${domainToHex(domain)}`;
|
||||
}
|
||||
|
||||
export function createDefaultMtprotoInboundSettings(): MtprotoInboundSettings {
|
||||
const fakeTlsDomain = 'www.cloudflare.com';
|
||||
return {
|
||||
fakeTlsDomain,
|
||||
secret: generateMtprotoSecret(fakeTlsDomain),
|
||||
};
|
||||
}
|
||||
|
||||
export function createDefaultTunnelInboundSettings(): TunnelInboundSettings {
|
||||
return {
|
||||
portMap: {},
|
||||
@@ -261,7 +299,8 @@ export type AnyInboundSettings =
|
||||
| MixedInboundSettings
|
||||
| TunInboundSettings
|
||||
| TunnelInboundSettings
|
||||
| WireguardInboundSettings;
|
||||
| WireguardInboundSettings
|
||||
| MtprotoInboundSettings;
|
||||
|
||||
export function createDefaultInboundSettings(protocol: string): AnyInboundSettings | null {
|
||||
switch (protocol) {
|
||||
@@ -275,6 +314,7 @@ export function createDefaultInboundSettings(protocol: string): AnyInboundSettin
|
||||
case 'tunnel': return createDefaultTunnelInboundSettings();
|
||||
case 'tun': return createDefaultTunInboundSettings();
|
||||
case 'wireguard': return createDefaultWireguardInboundSettings();
|
||||
case 'mtproto': return createDefaultMtprotoInboundSettings();
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -680,6 +680,28 @@ export function genHysteriaLink(input: GenHysteriaLinkInput): string {
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
export interface GenMtprotoLinkInput {
|
||||
inbound: Inbound;
|
||||
address: string;
|
||||
port?: number;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
// Builds a Telegram proxy deep link for an mtproto inbound:
|
||||
// tg://proxy?server=<addr>&port=<port>&secret=<ee FakeTLS secret>.
|
||||
export function genMtprotoLink(input: GenMtprotoLinkInput): string {
|
||||
const { inbound, address, port = inbound.port, remark = '' } = input;
|
||||
if (inbound.protocol !== 'mtproto') return '';
|
||||
const secret = inbound.settings.secret ?? '';
|
||||
if (secret.length === 0) return '';
|
||||
const url = new URL('tg://proxy');
|
||||
url.searchParams.set('server', address);
|
||||
url.searchParams.set('port', String(port));
|
||||
url.searchParams.set('secret', secret);
|
||||
url.hash = encodeURIComponent(remark);
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
export interface GenWireguardLinkInput {
|
||||
settings: WireguardInboundSettings;
|
||||
address: string;
|
||||
@@ -867,6 +889,8 @@ export function genLink(input: GenLinkInput): string {
|
||||
clientAuth: client.auth ?? '',
|
||||
externalProxy,
|
||||
});
|
||||
case 'mtproto':
|
||||
return genMtprotoLink({ inbound, address, port, remark });
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user