outbound: reverse Sniffing

This commit is contained in:
MHSanaei
2026-05-06 08:17:27 +02:00
parent 74e97fec4c
commit a8dff126c7
3 changed files with 102 additions and 3 deletions

View File

@@ -2617,12 +2617,14 @@ Inbound.VLESSSettings.VLESS = class extends Inbound.ClientBase {
id = RandomUtil.randomUUID(), id = RandomUtil.randomUUID(),
flow = '', flow = '',
reverseTag = '', reverseTag = '',
reverseSniffing = new Sniffing(),
email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at, email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at,
) { ) {
super(email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at); super(email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at);
this.id = id; this.id = id;
this.flow = flow; this.flow = flow;
this.reverseTag = reverseTag; this.reverseTag = reverseTag;
this.reverseSniffing = reverseSniffing;
} }
static fromJson(json = {}) { static fromJson(json = {}) {
@@ -2630,6 +2632,7 @@ Inbound.VLESSSettings.VLESS = class extends Inbound.ClientBase {
json.id, json.id,
json.flow, json.flow,
json.reverse?.tag ?? '', json.reverse?.tag ?? '',
Sniffing.fromJson(json.reverse?.sniffing || {}),
...Inbound.ClientBase.commonArgsFromJson(json), ...Inbound.ClientBase.commonArgsFromJson(json),
); );
} }
@@ -2641,7 +2644,9 @@ Inbound.VLESSSettings.VLESS = class extends Inbound.ClientBase {
...this._clientBaseToJson(), ...this._clientBaseToJson(),
}; };
if (this.reverseTag) { if (this.reverseTag) {
json.reverse = { tag: this.reverseTag }; json.reverse = {
tag: this.reverseTag,
};
} }
return json; return json;
} }

View File

@@ -50,6 +50,13 @@ const ALPN_OPTION = {
HTTP1: "http/1.1", HTTP1: "http/1.1",
}; };
const SNIFFING_OPTION = {
HTTP: "http",
TLS: "tls",
QUIC: "quic",
FAKEDNS: "fakedns"
};
const OutboundDomainStrategies = [ const OutboundDomainStrategies = [
"AsIs", "AsIs",
"UseIP", "UseIP",
@@ -170,6 +177,7 @@ Object.freeze(SSMethods);
Object.freeze(TLS_FLOW_CONTROL); Object.freeze(TLS_FLOW_CONTROL);
Object.freeze(UTLS_FINGERPRINT); Object.freeze(UTLS_FINGERPRINT);
Object.freeze(ALPN_OPTION); Object.freeze(ALPN_OPTION);
Object.freeze(SNIFFING_OPTION);
Object.freeze(OutboundDomainStrategies); Object.freeze(OutboundDomainStrategies);
Object.freeze(WireguardDomainStrategy); Object.freeze(WireguardDomainStrategy);
Object.freeze(USERS_SECURITY); Object.freeze(USERS_SECURITY);
@@ -196,6 +204,50 @@ class CommonClass {
} }
} }
class ReverseSniffing extends CommonClass {
constructor(
enabled = false,
destOverride = ['http', 'tls', 'quic', 'fakedns'],
metadataOnly = false,
routeOnly = false,
ipsExcluded = [],
domainsExcluded = [],
) {
super();
this.enabled = enabled;
this.destOverride = Array.isArray(destOverride) && destOverride.length > 0 ? destOverride : ['http', 'tls', 'quic', 'fakedns'];
this.metadataOnly = metadataOnly;
this.routeOnly = routeOnly;
this.ipsExcluded = Array.isArray(ipsExcluded) ? ipsExcluded : [];
this.domainsExcluded = Array.isArray(domainsExcluded) ? domainsExcluded : [];
}
static fromJson(json = {}) {
if (!json || Object.keys(json).length === 0) {
return new ReverseSniffing();
}
return new ReverseSniffing(
!!json.enabled,
json.destOverride,
json.metadataOnly,
json.routeOnly,
json.ipsExcluded || [],
json.domainsExcluded || [],
);
}
toJson() {
return {
enabled: this.enabled,
destOverride: this.destOverride,
metadataOnly: this.metadataOnly,
routeOnly: this.routeOnly,
ipsExcluded: this.ipsExcluded.length > 0 ? this.ipsExcluded : undefined,
domainsExcluded: this.domainsExcluded.length > 0 ? this.domainsExcluded : undefined,
};
}
}
class TcpStreamSettings extends CommonClass { class TcpStreamSettings extends CommonClass {
constructor(type = 'none', host, path) { constructor(type = 'none', host, path) {
super(); super();
@@ -1747,7 +1799,7 @@ Outbound.VmessSettings = class extends CommonClass {
} }
}; };
Outbound.VLESSSettings = class extends CommonClass { Outbound.VLESSSettings = class extends CommonClass {
constructor(address, port, id, flow, encryption, reverseTag = '', testpre = 0, testseed = [900, 500, 900, 256]) { constructor(address, port, id, flow, encryption, reverseTag = '', reverseSniffing = new ReverseSniffing(), testpre = 0, testseed = [900, 500, 900, 256]) {
super(); super();
this.address = address; this.address = address;
this.port = port; this.port = port;
@@ -1755,6 +1807,7 @@ Outbound.VLESSSettings = class extends CommonClass {
this.flow = flow; this.flow = flow;
this.encryption = encryption; this.encryption = encryption;
this.reverseTag = reverseTag; this.reverseTag = reverseTag;
this.reverseSniffing = reverseSniffing;
this.testpre = testpre; this.testpre = testpre;
this.testseed = testseed; this.testseed = testseed;
} }
@@ -1768,6 +1821,7 @@ Outbound.VLESSSettings = class extends CommonClass {
json.flow, json.flow,
json.encryption, json.encryption,
json.reverse?.tag || '', json.reverse?.tag || '',
ReverseSniffing.fromJson(json.reverse?.sniffing || {}),
json.testpre || 0, json.testpre || 0,
json.testseed && json.testseed.length >= 4 ? json.testseed : [900, 500, 900, 256] json.testseed && json.testseed.length >= 4 ? json.testseed : [900, 500, 900, 256]
); );
@@ -1782,7 +1836,12 @@ Outbound.VLESSSettings = class extends CommonClass {
encryption: this.encryption, encryption: this.encryption,
}; };
if (!ObjectUtil.isEmpty(this.reverseTag)) { if (!ObjectUtil.isEmpty(this.reverseTag)) {
result.reverse = { tag: this.reverseTag }; const reverseSniffing = this.reverseSniffing ? this.reverseSniffing.toJson() : {};
const defaultReverseSniffing = new ReverseSniffing().toJson();
result.reverse = {
tag: this.reverseTag,
sniffing: JSON.stringify(reverseSniffing) === JSON.stringify(defaultReverseSniffing) ? {} : reverseSniffing,
};
} }
// Only include Vision settings when flow is set // Only include Vision settings when flow is set
if (this.flow && this.flow !== '') { if (this.flow && this.flow !== '') {

View File

@@ -354,6 +354,41 @@
</template> </template>
<a-input v-model.trim="outbound.settings.reverseTag" :placeholder='`{{ i18n "pages.xray.outbound.reverseTagPlaceholder" }}`'></a-input> <a-input v-model.trim="outbound.settings.reverseTag" :placeholder='`{{ i18n "pages.xray.outbound.reverseTagPlaceholder" }}`'></a-input>
</a-form-item> </a-form-item>
<template v-if="outbound.settings.reverseTag">
<a-form-item>
<span slot="label">
Reverse Sniffing
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>
</template>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</span>
<a-switch v-model="outbound.settings.reverseSniffing.enabled"></a-switch>
</a-form-item>
<template v-if="outbound.settings.reverseSniffing.enabled">
<a-form-item :wrapper-col="{span:24}">
<a-checkbox-group v-model="outbound.settings.reverseSniffing.destOverride">
<a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox>
</a-checkbox-group>
</a-form-item>
<a-form-item label="Metadata Only">
<a-switch v-model="outbound.settings.reverseSniffing.metadataOnly"></a-switch>
</a-form-item>
<a-form-item label="Route Only">
<a-switch v-model="outbound.settings.reverseSniffing.routeOnly"></a-switch>
</a-form-item>
<a-form-item label="IPs Excluded">
<a-select mode="tags" v-model="outbound.settings.reverseSniffing.ipsExcluded" :style="{ width: '100%' }"
:token-separators="[',']" placeholder="IP/CIDR/geoip:*/ext:*"></a-select>
</a-form-item>
<a-form-item label="Domains Excluded">
<a-select mode="tags" v-model="outbound.settings.reverseSniffing.domainsExcluded" :style="{ width: '100%' }"
:token-separators="[',']" placeholder="domain:*/ext:*"></a-select>
</a-form-item>
</template>
</template>
</template> </template>
<template v-if="outbound.canEnableTlsFlow()"> <template v-if="outbound.canEnableTlsFlow()">
<a-form-item label="Flow"> <a-form-item label="Flow">