inbound : finalmask

This commit is contained in:
MHSanaei 2026-02-01 02:34:23 +01:00
parent c59f54bb0e
commit 3af6497577
No known key found for this signature in database
GPG key ID: 7E4060F2FBE5AB7A
6 changed files with 201 additions and 86 deletions

View file

@ -319,14 +319,12 @@ TcpStreamSettings.TcpResponse = class extends XrayCommonClass {
class KcpStreamSettings extends XrayCommonClass {
constructor(
mtu = 1350,
tti = 50,
tti = 20,
uplinkCapacity = 5,
downlinkCapacity = 20,
congestion = false,
readBufferSize = 2,
writeBufferSize = 2,
type = 'none',
seed = RandomUtil.randomSeq(10),
readBufferSize = 1,
writeBufferSize = 1,
) {
super();
this.mtu = mtu;
@ -336,8 +334,6 @@ class KcpStreamSettings extends XrayCommonClass {
this.congestion = congestion;
this.readBuffer = readBufferSize;
this.writeBuffer = writeBufferSize;
this.type = type;
this.seed = seed;
}
static fromJson(json = {}) {
@ -349,8 +345,6 @@ class KcpStreamSettings extends XrayCommonClass {
json.congestion,
json.readBufferSize,
json.writeBufferSize,
ObjectUtil.isEmpty(json.header) ? 'none' : json.header.type,
json.seed,
);
}
@ -363,10 +357,6 @@ class KcpStreamSettings extends XrayCommonClass {
congestion: this.congestion,
readBufferSize: this.readBuffer,
writeBufferSize: this.writeBuffer,
header: {
type: this.type,
},
seed: this.seed,
};
}
}
@ -929,6 +919,51 @@ class SockoptStreamSettings extends XrayCommonClass {
}
}
class FinalMask extends XrayCommonClass {
constructor(type = 'salamander', settings = {}) {
super();
this.type = type;
this.settings = this._getDefaultSettings(type, settings);
}
_getDefaultSettings(type, settings = {}) {
switch (type) {
case 'salamander':
case 'mkcp-aes128gcm':
return { password: settings.password || '' };
case 'header-dns':
case 'xdns':
return { domain: settings.domain || '' };
case 'mkcp-original':
case 'header-dtls':
case 'header-srtp':
case 'header-utp':
case 'header-wechat':
case 'header-wireguard':
return {};
default:
return settings;
}
}
static fromJson(json = {}) {
return new FinalMask(
json.type || 'salamander',
json.settings || {}
);
}
toJson() {
const result = {
type: this.type
};
if (this.settings && Object.keys(this.settings).length > 0) {
result.settings = this.settings;
}
return result;
}
}
class StreamSettings extends XrayCommonClass {
constructor(network = 'tcp',
security = 'none',
@ -941,6 +976,7 @@ class StreamSettings extends XrayCommonClass {
grpcSettings = new GrpcStreamSettings(),
httpupgradeSettings = new HTTPUpgradeStreamSettings(),
xhttpSettings = new xHTTPStreamSettings(),
finalmask = { udp: [] },
sockopt = undefined,
) {
super();
@ -955,9 +991,23 @@ class StreamSettings extends XrayCommonClass {
this.grpc = grpcSettings;
this.httpupgrade = httpupgradeSettings;
this.xhttp = xhttpSettings;
this.finalmask = finalmask;
this.sockopt = sockopt;
}
addUdpMask(type = 'salamander') {
if (!this.finalmask.udp) {
this.finalmask.udp = [];
}
this.finalmask.udp.push(new FinalMask(type));
}
delUdpMask(index) {
if (this.finalmask.udp) {
this.finalmask.udp.splice(index, 1);
}
}
get isTls() {
return this.security === "tls";
}
@ -992,6 +1042,14 @@ class StreamSettings extends XrayCommonClass {
}
static fromJson(json = {}) {
let finalmask = { udp: [] };
if (json.finalmask) {
if (Array.isArray(json.finalmask)) {
finalmask.udp = json.finalmask.map(mask => FinalMask.fromJson(mask));
} else if (json.finalmask.udp) {
finalmask.udp = json.finalmask.udp.map(mask => FinalMask.fromJson(mask));
}
}
return new StreamSettings(
json.network,
json.security,
@ -1004,6 +1062,7 @@ class StreamSettings extends XrayCommonClass {
GrpcStreamSettings.fromJson(json.grpcSettings),
HTTPUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
xHTTPStreamSettings.fromJson(json.xhttpSettings),
finalmask,
SockoptStreamSettings.fromJson(json.sockopt),
);
}
@ -1022,6 +1081,9 @@ class StreamSettings extends XrayCommonClass {
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
xhttpSettings: network === 'xhttp' ? this.xhttp.toJson() : undefined,
finalmask: (this.finalmask.udp && this.finalmask.udp.length > 0) ? {
udp: this.finalmask.udp.map(mask => mask.toJson())
} : undefined,
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
};
}
@ -1947,7 +2009,9 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
json.selectedAuth = this.selectedAuth;
}
if (this.testseed && this.testseed.length >= 4) {
// Only include testseed if at least one client has a flow set
const hasFlow = this.vlesses && this.vlesses.some(vless => vless.flow && vless.flow !== '');
if (hasFlow && this.testseed && this.testseed.length >= 4) {
json.testseed = this.testseed;
}

View file

@ -166,14 +166,12 @@ class TcpStreamSettings extends CommonClass {
class KcpStreamSettings extends CommonClass {
constructor(
mtu = 1350,
tti = 50,
tti = 20,
uplinkCapacity = 5,
downlinkCapacity = 20,
congestion = false,
readBufferSize = 2,
writeBufferSize = 2,
type = 'none',
seed = '',
readBufferSize = 1,
writeBufferSize = 1,
) {
super();
this.mtu = mtu;
@ -183,8 +181,6 @@ class KcpStreamSettings extends CommonClass {
this.congestion = congestion;
this.readBuffer = readBufferSize;
this.writeBuffer = writeBufferSize;
this.type = type;
this.seed = seed;
}
static fromJson(json = {}) {
@ -196,8 +192,6 @@ class KcpStreamSettings extends CommonClass {
json.congestion,
json.readBufferSize,
json.writeBufferSize,
ObjectUtil.isEmpty(json.header) ? 'none' : json.header.type,
json.seed,
);
}
@ -210,10 +204,6 @@ class KcpStreamSettings extends CommonClass {
congestion: this.congestion,
readBufferSize: this.readBuffer,
writeBufferSize: this.writeBuffer,
header: {
type: this.type,
},
seed: this.seed,
};
}
}

View file

@ -407,21 +407,6 @@
<!-- kcp -->
<template v-if="outbound.stream.network === 'kcp'">
<a-form-item label='{{ i18n "camouflage" }}'>
<a-select v-model="outbound.stream.kcp.type"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option>
<a-select-option value="srtp">SRTP</a-select-option>
<a-select-option value="utp">uTP</a-select-option>
<a-select-option value="wechat-video">WeChat</a-select-option>
<a-select-option value="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">WireGuard</a-select-option>
<a-select-option value="dns">DNS</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "password" }}'>
<a-input v-model="outbound.stream.kcp.seed"></a-input>
</a-form-item>
<a-form-item label='MTU'>
<a-input-number v-model.number="outbound.stream.kcp.mtu"
min="0"></a-input-number>
@ -607,7 +592,7 @@
<template v-if="outbound.canEnableStream()">
<a-form-item label="UDP Masks">
<a-button icon="plus" type="primary" size="small"
@click="outbound.stream.addUdpMask(outbound.protocol === Protocols.Hysteria ? 'salamander' : 'mkcp-aes128gcm')"></a-button>
@click="outbound.stream.addUdpMask(outbound.protocol === Protocols.Hysteria ? 'salamander' : (outbound.stream.network === 'kcp' ? 'mkcp-aes128gcm' : 'xdns'))"></a-button>
</a-form-item>
<template
v-if="outbound.stream.finalmask.udp && outbound.stream.finalmask.udp.length > 0">
@ -623,25 +608,39 @@
<a-select v-model="mask.type"
@change="(type) => mask.settings = mask._getDefaultSettings(type, {})"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-if="outbound.protocol === Protocols.Hysteria" value="salamander">
<!-- Salamander for Hysteria2 only -->
<a-select-option v-if="outbound.protocol === Protocols.Hysteria"
value="salamander">
Salamander (Hysteria2)</a-select-option>
<a-select-option value="mkcp-aes128gcm">
<!-- mKCP-specific masks -->
<a-select-option v-if="outbound.stream.network === 'kcp'"
value="mkcp-aes128gcm">
mKCP AES-128-GCM</a-select-option>
<a-select-option value="header-dns">
<a-select-option v-if="outbound.stream.network === 'kcp'"
value="header-dns">
Header DNS</a-select-option>
<a-select-option value="header-dtls">
<a-select-option v-if="outbound.stream.network === 'kcp'"
value="header-dtls">
Header DTLS 1.2</a-select-option>
<a-select-option value="header-srtp">
<a-select-option v-if="outbound.stream.network === 'kcp'"
value="header-srtp">
Header SRTP</a-select-option>
<a-select-option value="header-utp">
<a-select-option v-if="outbound.stream.network === 'kcp'"
value="header-utp">
Header uTP</a-select-option>
<a-select-option value="header-wechat">
<a-select-option v-if="outbound.stream.network === 'kcp'"
value="header-wechat">
Header WeChat Video</a-select-option>
<a-select-option value="header-wireguard">
<a-select-option v-if="outbound.stream.network === 'kcp'"
value="header-wireguard">
Header WireGuard</a-select-option>
<a-select-option value="mkcp-original">
<a-select-option v-if="outbound.stream.network === 'kcp'"
value="mkcp-original">
mKCP Original</a-select-option>
<a-select-option value="xdns">
<!-- xDNS for TCP/WS/HTTPUpgrade/XHTTP -->
<a-select-option
v-if="['tcp', 'ws', 'httpupgrade', 'xhttp'].includes(outbound.stream.network)"
value="xdns">
xDNS (Experimental)</a-select-option>
</a-select>
</a-form-item>

View file

@ -0,0 +1,70 @@
{{define "form/streamFinalMask"}}
<a-divider :style="{ margin: '5px 0 0' }"></a-divider>
<a-form :colon="false" :label-col="{ md: {span:8} }"
:wrapper-col="{ md: {span:14} }">
<a-form-item label="UDP Masks">
<a-button icon="plus" type="primary" size="small"
@click="inbound.stream.addUdpMask(inbound.stream.network === 'kcp' ? 'mkcp-aes128gcm' : 'xdns')"></a-button>
</a-form-item>
<template
v-if="inbound.stream.finalmask.udp && inbound.stream.finalmask.udp.length > 0">
<a-form v-for="(mask, index) in inbound.stream.finalmask.udp"
:key="index" :colon="false"
:label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-divider :style="{ margin: '0' }"> UDP Mask [[ index + 1 ]]
<a-icon type="delete"
@click="() => inbound.stream.delUdpMask(index)"
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
</a-divider>
<a-form-item label='Type'>
<a-select v-model="mask.type"
@change="(type) => mask.settings = mask._getDefaultSettings(type, {})"
:dropdown-class-name="themeSwitcher.currentTheme">
<!-- mKCP-specific masks -->
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="mkcp-aes128gcm">
mKCP AES-128-GCM</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="header-dns">
Header DNS</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="header-dtls">
Header DTLS 1.2</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="header-srtp">
Header SRTP</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="header-utp">
Header uTP</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="header-wechat">
Header WeChat Video</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="header-wireguard">
Header WireGuard</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="mkcp-original">
mKCP Original</a-select-option>
<!-- xDNS for TCP/WS/HTTPUpgrade/XHTTP -->
<a-select-option
v-if="['tcp', 'ws', 'httpupgrade', 'xhttp'].includes(inbound.stream.network)"
value="xdns">
xDNS (Experimental)</a-select-option>
</a-select>
</a-form-item>
<!-- Settings for password-based masks -->
<a-form-item label='Password'
v-if="['mkcp-aes128gcm'].includes(mask.type)">
<a-input v-model.trim="mask.settings.password"
placeholder="Obfuscation password"></a-input>
</a-form-item>
<!-- Settings for domain-based masks -->
<a-form-item label='Domain'
v-if="['header-dns', 'xdns'].includes(mask.type)">
<a-input v-model.trim="mask.settings.domain"
placeholder="e.g., www.example.com"></a-input>
</a-form-item>
</a-form>
</template>
</a-form>
{{end}}

View file

@ -1,48 +1,32 @@
{{define "form/streamKCP"}}
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "camouflage" }}'>
<a-select v-model="inbound.stream.kcp.type" :style="{ width: '50%' }" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option>
<a-select-option value="srtp">SRTP</a-select-option>
<a-select-option value="utp">uTP</a-select-option>
<a-select-option value="wechat-video">WeChat</a-select-option>
<a-select-option value="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">WireGuard</a-select-option>
<a-select-option value="dns">DNS</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "password" }}
<a-icon @click="inbound.stream.kcp.seed = RandomUtil.randomSeq(10)"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="inbound.stream.kcp.seed"></a-input>
</a-form-item>
<a-form :colon="false" :label-col="{ md: {span:8} }"
:wrapper-col="{ md: {span:14} }">
<a-form-item label='MTU'>
<a-input-number v-model.number="inbound.stream.kcp.mtu" :min="576" :max="1460"></a-input-number>
<a-input-number v-model.number="inbound.stream.kcp.mtu" :min="576"
:max="1460"></a-input-number>
</a-form-item>
<a-form-item label='TTI (ms)'>
<a-input-number v-model.number="inbound.stream.kcp.tti" :min="10" :max="100"></a-input-number>
<a-input-number v-model.number="inbound.stream.kcp.tti" :min="10"
:max="100"></a-input-number>
</a-form-item>
<a-form-item label='Uplink (MB/s)'>
<a-input-number v-model.number="inbound.stream.kcp.upCap" :min="0"></a-input-number>
<a-input-number v-model.number="inbound.stream.kcp.upCap"
:min="0"></a-input-number>
</a-form-item>
<a-form-item label='Downlink (MB/s)'>
<a-input-number v-model.number="inbound.stream.kcp.downCap" :min="0"></a-input-number>
<a-input-number v-model.number="inbound.stream.kcp.downCap"
:min="0"></a-input-number>
</a-form-item>
<a-form-item label='Congestion'>
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
</a-form-item>
<a-form-item label='Read Buffer (MB)'>
<a-input-number v-model.number="inbound.stream.kcp.readBuffer" :min="0"></a-input-number>
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"
:min="0"></a-input-number>
</a-form-item>
<a-form-item label='Write Buffer (MB)'>
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer" :min="0"></a-input-number>
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"
:min="0"></a-input-number>
</a-form-item>
</a-form>
{{end}}

View file

@ -1,8 +1,10 @@
{{define "form/streamSettings"}}
<!-- select stream network -->
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form :colon="false" :label-col="{ md: {span:8} }"
:wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "transmission" }}'>
<a-select v-model="inbound.stream.network" :style="{ width: '75%' }" @change="streamNetworkChange"
<a-select v-model="inbound.stream.network" :style="{ width: '75%' }"
@change="streamNetworkChange"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp">TCP (RAW)</a-select-option>
<a-select-option value="kcp">mKCP</a-select-option>
@ -48,4 +50,10 @@
<template>
{{template "form/streamSockopt"}}
</template>
<!-- finalmask - only for TCP, WS, HTTPUpgrade, XHTTP, mKCP -->
<template
v-if="['tcp', 'ws', 'httpupgrade', 'xhttp', 'kcp'].includes(inbound.stream.network)">
{{template "form/streamFinalMask"}}
</template>
{{end}}