feat(xray): add the XDRIVE transport

xray-core v26.10.10 tunnels a stream through files in shared cloud
storage ("network": "xdrive"): Google Drive, a local folder, or any HTTP
storage API described by a template. The panel could not select it, and
the stream schema rejected a pasted xdrive stream outright.

The inbound and outbound forms now offer XDRIVE with one shared editor:
service, folder, the three Google Drive secrets in the order the core
reads them, a template JSON editor with its {secretN} values, the
optional domain front (the stream-level address/port the storage client
dials), and the core's tuning knobs with their defaults as placeholders.
Switching an inbound to XDRIVE resets security to none, because the
core would otherwise read the inbound's TLS/REALITY as the storage API's
client TLS.

XDRIVE has no share-link format, and a link carrying the storage
secrets would print them into every QR code, so both link generators
emit none for it; the JSON subscription already passes the stream
through intact, and Clash drops the unknown transport.
This commit is contained in:
MHSanaei
2026-10-11 08:21:08 +02:00
parent 73a028f52d
commit ca5983595b
34 changed files with 848 additions and 19 deletions
@@ -54,8 +54,10 @@ import { WsStreamSettingsSchema } from '@/schemas/protocols/stream/ws';
import { GrpcStreamSettingsSchema } from '@/schemas/protocols/stream/grpc';
import { HttpUpgradeStreamSettingsSchema } from '@/schemas/protocols/stream/httpupgrade';
import { XHttpStreamSettingsSchema } from '@/schemas/protocols/stream/xhttp';
import { XDriveStreamSettingsSchema } from '@/schemas/protocols/stream/xdrive';
import { DateTimePicker } from '@/components/form';
import { FinalMaskField } from '@/lib/xray/forms/fields';
import XDriveForm from '@/lib/xray/forms/transport/XDriveForm';
import './InboundFormModal.css';
import { AdvancedAllEditor, AdvancedSliceEditor } from './advanced-editors';
@@ -214,6 +216,8 @@ function newStreamSlice(n: string): Record<string, unknown> {
return HttpUpgradeStreamSettingsSchema.parse({});
case 'xhttp':
return XHttpStreamSettingsSchema.parse({});
case 'xdrive':
return XDriveStreamSettingsSchema.parse({});
default:
return {};
}
@@ -865,12 +869,22 @@ export default function InboundFormModal({
'grpcSettings',
'httpupgradeSettings',
'xhttpSettings',
'xdriveSettings',
];
const current = (getV('streamSettings') as Record<string, unknown>) ?? {};
const cleaned: Record<string, unknown> = { ...current, network: next };
for (const k of ALL) {
if (k !== `${next}Settings`) delete cleaned[k];
}
if (next === 'xdrive') {
/* The core would read the inbound's TLS as the storage API's client TLS. */
cleaned.security = 'none';
delete cleaned.tlsSettings;
delete cleaned.realitySettings;
} else {
delete cleaned.address;
delete cleaned.port;
}
cleaned[`${next}Settings`] = newStreamSlice(next);
if (next === 'kcp') {
const fm = (cleaned.finalmask as Record<string, unknown> | undefined) ?? {};
@@ -918,6 +932,7 @@ export default function InboundFormModal({
{ value: 'grpc', label: 'gRPC' },
{ value: 'httpupgrade', label: 'HTTPUpgrade' },
{ value: 'xhttp', label: 'XHTTP' },
{ value: 'xdrive', label: 'XDRIVE' },
]}
/>
</Form.Item>
@@ -941,6 +956,8 @@ export default function InboundFormModal({
{network === 'httpupgrade' && <HttpUpgradeForm />}
{network === 'kcp' && <KcpForm />}
{network === 'xdrive' && <XDriveForm />}
</>
)}
@@ -8,6 +8,7 @@ import { JsonEditor } from '@/components/form';
import { Wireguard } from '@/utils';
import { formValuesToWirePayload, rawOutboundToFormValues } from '@/lib/xray/outbound-form-adapter';
import { parseOutboundLink } from '@/lib/xray/outbound-link-parser';
import XDriveForm from '@/lib/xray/forms/transport/XDriveForm';
import { XMUX_FRESH_DEFAULTS } from '@/schemas/protocols/stream/xhttp';
import { OutboundFormBaseSchema, type OutboundFormValues } from '@/schemas/forms/outbound-form';
import {
@@ -487,6 +488,8 @@ export default function OutboundFormModal({
{network === 'xhttp' && <XhttpForm onXmuxToggle={onXmuxToggle} />}
{network === 'hysteria' && <HysteriaForm />}
{network === 'xdrive' && <XDriveForm />}
</>
)}
@@ -44,6 +44,7 @@ export const NETWORK_OPTIONS: { value: string; label: string }[] = [
{ value: 'grpc', label: 'gRPC' },
{ value: 'httpupgrade', label: 'HTTPUpgrade' },
{ value: 'xhttp', label: 'XHTTP' },
{ value: 'xdrive', label: 'XDRIVE' },
];
// The hysteria protocol is locked to its own QUIC transport: the selector
@@ -1,6 +1,7 @@
import { rawOutboundToFormValues } from '@/lib/xray/outbound-form-adapter';
import { canEnableReality, canEnableTls } from '@/lib/xray/protocol-capabilities';
import type { OutboundFormValues } from '@/schemas/forms/outbound-form';
import { XDriveStreamSettingsSchema } from '@/schemas/protocols/stream/xdrive';
import { MUX_PROTOCOLS } from './outbound-form-constants';
@@ -64,6 +65,8 @@ export function newStreamSlice(network: string): Record<string, unknown> {
udpIdleTimeout: 60,
},
};
case 'xdrive':
return { network: 'xdrive', xdriveSettings: XDriveStreamSettingsSchema.parse({}) };
default:
return { network: 'tcp', tcpSettings: { header: { type: 'none' } } };
}