mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-29 06:27:14 +00:00
fix: expose streamSettings for Tunnel inbounds to support TProxy (#5171)
* fix: expose streamSettings for Tunnel inbounds to support TProxy * fix(ui): hide security tab for tunnel inbounds when stream is enabled tunnel (dokodemo-door) does not support TLS or Reality, so showing the security tab only results in a fully-disabled radio group. Exclude tunnel alongside wireguard from the security tab. * fix(tunnel): restrict stream tab to sockopt-only and fix transportless schema Tunnel (dokodemo-door) only needs sockopt.tproxy for TProxy mode — no user-selectable transport. Add hasSelectableTransport flag to hide the network picker, per-network sub-forms, ExternalProxy, and FinalMask for both tunnel and wireguard, matching the pattern already used for Hysteria. Fix a pre-existing Zod schema bug where NetworkSettingsSchema was a bare discriminatedUnion requiring `network` to be present. Wireguard and tunnel submit streamSettings without a `network` key, causing "Invalid discriminator value. Expected 'tcp' | ..." on every save. Fix by adding a transportless union branch (z.never().optional()) alongside the transport DU; also add ?? 'tcp' fallback in inbound-link.ts where stream.network is now string | undefined. Three regression tests added. --------- Co-authored-by: rqzbeh <rqzbeh@users.noreply.github.com> Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
@@ -162,6 +162,15 @@ export default function InboundFormModal({
|
||||
const security = Form.useWatch(['streamSettings', 'security'], form) ?? 'none';
|
||||
const streamEnabled = canEnableStream({ protocol });
|
||||
const sniffingSupported = canEnableSniffing({ protocol });
|
||||
// Wireguard (always a UDP listener) and Tunnel (dokodemo-door) expose no
|
||||
// user-selectable transport — their stream tab is just sockopt, which is all
|
||||
// Tunnel's TProxy/redirect mode needs (sockopt.tproxy). Hysteria carries its
|
||||
// own dedicated transport form. For all of these the RAW/mKCP/WS/... network
|
||||
// picker and the per-network sub-forms are hidden.
|
||||
const hasSelectableTransport =
|
||||
protocol !== Protocols.HYSTERIA
|
||||
&& protocol !== Protocols.WIREGUARD
|
||||
&& protocol !== Protocols.TUNNEL;
|
||||
|
||||
const wPort = Form.useWatch('port', form);
|
||||
const wListen = (Form.useWatch('listen', form) ?? '') as string;
|
||||
@@ -372,11 +381,13 @@ export default function InboundFormModal({
|
||||
}],
|
||||
},
|
||||
});
|
||||
} else if (next === Protocols.WIREGUARD) {
|
||||
// Wireguard has no user-selectable transport: the listener is always
|
||||
// UDP and only finalmask/sockopt from streamSettings apply. Drop the
|
||||
// leftover network/transport slices so the stream tab doesn't render
|
||||
// a TCP sub-form and the wire payload carries no dead tcpSettings.
|
||||
} else if (next === Protocols.WIREGUARD || next === Protocols.TUNNEL) {
|
||||
// Wireguard and Tunnel (dokodemo-door) have no user-selectable
|
||||
// transport: wireguard is always a UDP listener, and tunnel only needs
|
||||
// `sockopt.tproxy` for its TProxy/redirect mode. Drop the leftover
|
||||
// network/transport slices so the stream tab doesn't render a TCP
|
||||
// sub-form and the wire payload carries no dead tcpSettings — the
|
||||
// sockopt section (with TProxy) stays available.
|
||||
form.setFieldValue('streamSettings', { security: 'none' });
|
||||
} else {
|
||||
const current = form.getFieldValue('streamSettings') as { network?: string } | undefined;
|
||||
@@ -651,7 +662,7 @@ export default function InboundFormModal({
|
||||
|
||||
const streamTab = (
|
||||
<>
|
||||
{protocol !== Protocols.HYSTERIA && protocol !== Protocols.WIREGUARD && (
|
||||
{hasSelectableTransport && (
|
||||
<Form.Item label={t('transmission')} name={['streamSettings', 'network']}>
|
||||
<Select
|
||||
style={{ width: '75%' }}
|
||||
@@ -677,31 +688,41 @@ export default function InboundFormModal({
|
||||
HTTP server when probed. */}
|
||||
{protocol === Protocols.HYSTERIA && <HysteriaFields form={form} />}
|
||||
|
||||
{network === 'tcp' && <RawForm />}
|
||||
{hasSelectableTransport && (
|
||||
<>
|
||||
{network === 'tcp' && <RawForm />}
|
||||
|
||||
{network === 'ws' && <WsForm />}
|
||||
{network === 'ws' && <WsForm />}
|
||||
|
||||
{network === 'grpc' && <GrpcForm />}
|
||||
{network === 'grpc' && <GrpcForm />}
|
||||
|
||||
{network === 'xhttp' && <XhttpForm form={form} />}
|
||||
{network === 'xhttp' && <XhttpForm form={form} />}
|
||||
|
||||
{network === 'httpupgrade' && <HttpUpgradeForm />}
|
||||
{network === 'httpupgrade' && <HttpUpgradeForm />}
|
||||
|
||||
{network === 'kcp' && <KcpForm />}
|
||||
{network === 'kcp' && <KcpForm />}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* externalProxy only feeds client share links, and wireguard's
|
||||
per-peer .conf fanout resolves its host elsewhere — the section
|
||||
would be dead weight on a wireguard inbound. */}
|
||||
{protocol !== Protocols.WIREGUARD && <ExternalProxyForm toggleExternalProxy={toggleExternalProxy} />}
|
||||
{/* externalProxy only feeds client share links. Wireguard's per-peer
|
||||
.conf fanout resolves its host elsewhere, and tunnel (dokodemo-door)
|
||||
has no clients at all — the section is dead weight on both. */}
|
||||
{protocol !== Protocols.WIREGUARD && protocol !== Protocols.TUNNEL && (
|
||||
<ExternalProxyForm toggleExternalProxy={toggleExternalProxy} />
|
||||
)}
|
||||
|
||||
<SockoptForm toggleSockopt={toggleSockopt} />
|
||||
|
||||
<FinalMaskForm
|
||||
name={['streamSettings', 'finalmask']}
|
||||
network={network as string}
|
||||
protocol={protocol}
|
||||
form={form}
|
||||
/>
|
||||
{/* Transport masks don't apply to tunnel (a transparent forwarder), so
|
||||
its stream tab is just sockopt + TProxy. */}
|
||||
{protocol !== Protocols.TUNNEL && (
|
||||
<FinalMaskForm
|
||||
name={['streamSettings', 'finalmask']}
|
||||
network={network as string}
|
||||
protocol={protocol}
|
||||
form={form}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -906,9 +927,9 @@ export default function InboundFormModal({
|
||||
...(streamEnabled
|
||||
? [
|
||||
{ key: 'stream', label: t('pages.inbounds.streamTab'), children: streamTab, forceRender: true },
|
||||
// Wireguard can't do TLS/Reality (canEnableTls is false), so
|
||||
// Wireguard and Tunnel can't do TLS/Reality (canEnableTls is false), so
|
||||
// the security tab would only show a fully disabled radio.
|
||||
...(protocol !== Protocols.WIREGUARD
|
||||
...(protocol !== Protocols.WIREGUARD && protocol !== Protocols.TUNNEL
|
||||
? [{ key: 'security', label: t('pages.inbounds.securityTab'), children: securityTab, forceRender: true }]
|
||||
: []),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user