mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-13 09:36:05 +00:00
feat(xray): add loopback outbound protocol
#4185 Surface xray-core's loopback outbound in the Outbounds form so users can re-route already-processed traffic back into a named inbound for secondary routing (e.g. splitting TCP/UDP from one ingress). The inboundTag field is an autocomplete over existing inbound tags, with free-text fallback for inbounds defined outside the panel. Loopback outbounds are excluded from the connectivity test since they have no network endpoint.
This commit is contained in:
parent
917f9b307e
commit
60e2af088d
4 changed files with 47 additions and 1 deletions
|
|
@ -12,6 +12,7 @@ export const Protocols = {
|
|||
Hysteria: "hysteria",
|
||||
Socks: "socks",
|
||||
HTTP: "http",
|
||||
Loopback: "loopback",
|
||||
};
|
||||
|
||||
export const SSMethods = {
|
||||
|
|
@ -1586,6 +1587,7 @@ Outbound.Settings = class extends CommonClass {
|
|||
case Protocols.HTTP: return new Outbound.HttpSettings();
|
||||
case Protocols.Wireguard: return new Outbound.WireguardSettings();
|
||||
case Protocols.Hysteria: return new Outbound.HysteriaSettings();
|
||||
case Protocols.Loopback: return new Outbound.LoopbackSettings();
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1603,6 +1605,7 @@ Outbound.Settings = class extends CommonClass {
|
|||
case Protocols.HTTP: return Outbound.HttpSettings.fromJson(json);
|
||||
case Protocols.Wireguard: return Outbound.WireguardSettings.fromJson(json);
|
||||
case Protocols.Hysteria: return Outbound.HysteriaSettings.fromJson(json);
|
||||
case Protocols.Loopback: return Outbound.LoopbackSettings.fromJson(json);
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1782,6 +1785,23 @@ Outbound.BlackholeSettings = class extends CommonClass {
|
|||
}
|
||||
};
|
||||
|
||||
Outbound.LoopbackSettings = class extends CommonClass {
|
||||
constructor(inboundTag = '') {
|
||||
super();
|
||||
this.inboundTag = inboundTag;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
return new Outbound.LoopbackSettings(json.inboundTag || '');
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
inboundTag: this.inboundTag || undefined,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Outbound.DNSRule = class extends CommonClass {
|
||||
constructor(action = 'direct', qtype = '', domain = '') {
|
||||
super();
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ const props = defineProps({
|
|||
open: { type: Boolean, default: false },
|
||||
outbound: { type: Object, default: null },
|
||||
existingTags: { type: Array, default: () => [] },
|
||||
inboundTags: { type: Array, default: () => [] },
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:open', 'confirm']);
|
||||
|
|
@ -199,6 +200,7 @@ const isBlackhole = computed(() => proto.value === Protocols.Blackhole);
|
|||
const isDNS = computed(() => proto.value === Protocols.DNS);
|
||||
const isWireguard = computed(() => proto.value === Protocols.Wireguard);
|
||||
const isHysteria = computed(() => proto.value === Protocols.Hysteria);
|
||||
const isLoopback = computed(() => proto.value === Protocols.Loopback);
|
||||
|
||||
function regenerateWgKeys() {
|
||||
if (!outbound.value?.settings) return;
|
||||
|
|
@ -311,6 +313,16 @@ function regenerateWgKeys() {
|
|||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- ============== Loopback ============== -->
|
||||
<template v-if="isLoopback">
|
||||
<a-form-item label="Inbound tag">
|
||||
<a-auto-complete v-model:value="outbound.settings.inboundTag"
|
||||
:options="inboundTags.map((tag) => ({ value: tag }))"
|
||||
:filter-option="(input, option) => option.value.toLowerCase().includes(input.toLowerCase())"
|
||||
placeholder="tag of an existing inbound to re-route into" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- ============== DNS ============== -->
|
||||
<template v-if="isDNS">
|
||||
<a-form-item :label="t('pages.inbounds.network')">
|
||||
|
|
|
|||
|
|
@ -35,9 +35,19 @@ const props = defineProps({
|
|||
templateSettings: { type: Object, default: null },
|
||||
outboundsTraffic: { type: Array, default: () => [] },
|
||||
outboundTestStates: { type: Object, default: () => ({}) },
|
||||
inboundTags: { type: Array, default: () => [] },
|
||||
isMobile: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const inboundTagOptions = computed(() => {
|
||||
const out = new Set();
|
||||
for (const ib of props.templateSettings?.inbounds || []) {
|
||||
if (ib.tag) out.add(ib.tag);
|
||||
}
|
||||
for (const t of props.inboundTags || []) out.add(t);
|
||||
return [...out];
|
||||
});
|
||||
|
||||
const emit = defineEmits(['reset-traffic', 'test', 'show-warp', 'show-nord']);
|
||||
|
||||
// === Modal state ====================================================
|
||||
|
|
@ -129,7 +139,9 @@ function outboundAddresses(o) {
|
|||
}
|
||||
|
||||
function isUntestable(o) {
|
||||
return o.protocol === 'blackhole' || o.tag === 'blocked';
|
||||
return o.protocol === Protocols.Blackhole
|
||||
|| o.protocol === Protocols.Loopback
|
||||
|| o.tag === 'blocked';
|
||||
}
|
||||
function isTesting(idx) {
|
||||
return !!props.outboundTestStates?.[idx]?.testing;
|
||||
|
|
@ -377,6 +389,7 @@ const rows = computed(() => {
|
|||
v-model:open="modalOpen"
|
||||
:outbound="editingOutbound"
|
||||
:existing-tags="existingTags"
|
||||
:inbound-tags="inboundTagOptions"
|
||||
@confirm="onConfirm"
|
||||
/>
|
||||
</a-space>
|
||||
|
|
|
|||
|
|
@ -294,6 +294,7 @@ function confirmRestart() {
|
|||
:template-settings="templateSettings"
|
||||
:outbounds-traffic="outboundsTraffic"
|
||||
:outbound-test-states="outboundTestStates"
|
||||
:inbound-tags="inboundTags"
|
||||
:is-mobile="isMobile"
|
||||
@reset-traffic="resetOutboundsTraffic"
|
||||
@test="onTestOutbound"
|
||||
|
|
|
|||
Loading…
Reference in a new issue