wireguard outbound

Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
This commit is contained in:
MHSanaei 2024-01-10 16:14:18 +03:30
parent 722f5e716f
commit ee703ad857
3 changed files with 247 additions and 258 deletions

View file

@ -8,7 +8,7 @@ const Protocols = {
Shadowsocks: "shadowsocks", Shadowsocks: "shadowsocks",
Socks: "socks", Socks: "socks",
HTTP: "http", HTTP: "http",
Wireguard: "wireguard", Wireguard: "wireguard"
}; };
const SSMethods = { const SSMethods = {
@ -47,12 +47,12 @@ const ALPN_OPTION = {
HTTP1: "http/1.1", HTTP1: "http/1.1",
}; };
const outboundDomainStrategies = [ const OutboundDomainStrategies = [
"AsIs", "AsIs",
"UseIP", "UseIP",
"UseIPv4", "UseIPv4",
"UseIPv6" "UseIPv6"
] ];
const WireguardDomainStrategy = [ const WireguardDomainStrategy = [
"ForceIP", "ForceIP",
@ -66,7 +66,7 @@ Object.freeze(Protocols);
Object.freeze(SSMethods); Object.freeze(SSMethods);
Object.freeze(TLS_FLOW_CONTROL); Object.freeze(TLS_FLOW_CONTROL);
Object.freeze(ALPN_OPTION); Object.freeze(ALPN_OPTION);
Object.freeze(outboundDomainStrategies); Object.freeze(OutboundDomainStrategies);
Object.freeze(WireguardDomainStrategy); Object.freeze(WireguardDomainStrategy);
class CommonClass { class CommonClass {
@ -850,105 +850,6 @@ Outbound.ShadowsocksSettings = class extends CommonClass {
}; };
} }
}; };
Outbound.WireguardSettings = class extends CommonClass {
constructor(secretKey, address, peers, mtu, workers, domainStrategy, reserved) {
super();
this.secretKey = secretKey || '';
this.address = address ? [...address] : [];
this.peers = peers ? peers.map((p) => ({
...p,
allowedIPs: p.allowedIPs ? [...p.allowedIPs] : [],
})) : [];
this.mtu = mtu;
this.workers = workers;
this.domainStrategy = domainStrategy;
this.reserved = reserved;
}
static fromJson(json={}) {
return new Outbound.WireguardSettings(
json.secretKey,
json.address,
json.peers,
json.mtu,
json.workers,
json.domainStrategy,
json.reserved,
);
}
addAddress() {
this.address.push('');
}
delAddress(index) {
this.address.splice(index, 1);
}
addPeer() {
this.peers.push({
endpoint: '',
publicKey: '',
allowedIPs: [],
});
}
delPeer(index) {
this.peers.splice(index, 1);
}
addAllowedIP(index) {
if (!this.peers[index].allowedIPs) {
this.peers[index].allowedIPs = [];
}
this.peers[index].allowedIPs.push('');
}
delAllowedIP(index, ipIndex) {
this.peers[index].allowedIPs.splice(ipIndex, 1);
}
optionalFields = ['mtu', 'workers', 'domainStrategy', 'address', 'reserved'];
optionalPeerFields = ['allowedIPs', 'keepAlive', 'preSharedKey'];
cleanUpOptionalFields(obj) {
const isEmpty = (v) => ObjectUtil.isEmpty(v) || ObjectUtil.isArrEmpty(v);
return Object.entries(obj).reduce((memo, [key, value]) => {
if (key === 'peers') {
memo[key] = value.map((peer) => {
return Object.entries(peer).reduce((pMemo, [pKey, pValue]) => {
if (this.optionalPeerFields.includes(pKey) && isEmpty(pValue)) {
return pMemo;
}
pMemo[pKey] = pValue;
return pMemo;
}, {});
});
} else if (this.optionalFields.includes(key) && isEmpty(value)) {
return memo;
} else {
memo[key] = value;
}
return memo;
}, {});
}
toJson() {
return this.cleanUpOptionalFields({
secretKey: this.secretKey,
address: this.address,
peers: this.peers,
mtu: this.mtu,
workers: this.workers,
domainStrategy: this.domainStrategy,
reserved: this.reserved,
});
}
};
Outbound.SocksSettings = class extends CommonClass { Outbound.SocksSettings = class extends CommonClass {
constructor(address, port, user, pass) { constructor(address, port, user, pass) {
@ -1009,4 +910,87 @@ Outbound.HttpSettings = class extends CommonClass {
}], }],
}; };
} }
};
Outbound.WireguardSettings = class extends CommonClass {
constructor(
mtu=1420, secretKey=Wireguard.generateKeypair().privateKey,
address='', workers=2, domainStrategy='', reserved='',
peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) {
super();
this.mtu = mtu;
this.secretKey = secretKey;
this.pubKey = secretKey.length>0 ? Wireguard.generateKeypair(secretKey).publicKey : '';
this.address = address instanceof Array ? address.join(',') : address;
this.workers = workers;
this.domainStrategy = domainStrategy;
this.reserved = reserved instanceof Array ? reserved.join(',') : reserved;
this.peers = peers;
this.kernelMode = kernelMode;
}
addPeer() {
this.peers.push(new Outbound.WireguardSettings.Peer());
}
delPeer(index) {
this.peers.splice(index, 1);
}
static fromJson(json={}){
return new Outbound.WireguardSettings(
json.mtu,
json.secretKey,
json.address,
json.workers,
json.domainStrategy,
json.reserved,
json.peers.map(peer => Outbound.WireguardSettings.Peer.fromJson(peer)),
json.kernelMode,
);
}
toJson() {
return {
mtu: this.mtu?? undefined,
secretKey: this.secretKey,
address: this.address ? this.address.split(",") : [],
workers: this.workers?? undefined,
domainStrategy: WireguardDomainStrategy.includes(this.domainStrategy) ? this.domainStrategy : undefined,
reserved: this.reserved ? this.reserved.split(",") : undefined,
peers: Outbound.WireguardSettings.Peer.toJsonArray(this.peers),
kernelMode: this.kernelMode,
};
}
};
Outbound.WireguardSettings.Peer = class extends CommonClass {
constructor(publicKey='', psk='', allowedIPs=['0.0.0.0/0','::/0'], endpoint='', keepAlive=0) {
super();
this.publicKey = publicKey;
this.psk = psk;
this.allowedIPs = allowedIPs;
this.endpoint = endpoint;
this.keepAlive = keepAlive;
}
static fromJson(json={}){
return new Outbound.WireguardSettings.Peer(
json.publicKey,
json.preSharedKey,
json.allowedIPs,
json.endpoint,
json.keepAlive
);
}
toJson() {
return {
publicKey: this.publicKey,
preSharedKey: this.psk.length>0 ? this.psk : undefined,
allowedIPs: this.allowedIPs ? this.allowedIPs.split(",") : undefined,
endpoint: this.endpoint,
keepAlive: this.keepAlive?? undefined,
};
}
}; };

View file

@ -18,7 +18,7 @@
<a-select <a-select
v-model="outbound.settings.domainStrategy" v-model="outbound.settings.domainStrategy"
:dropdown-class-name="themeSwitcher.currentTheme"> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in outboundDomainStrategies" :value="s">[[ s ]]</a-select-option> <a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='Fragment'> <a-form-item label='Fragment'>
@ -44,108 +44,116 @@
</template> </template>
</template> </template>
<!-- wireguard settings -->
<template v-if="outbound.protocol === Protocols.Wireguard">
<a-form-item label='Secret Key'>
<a-input v-model.trim="outbound.settings.secretKey"></a-input>
</a-form-item>
<a-form-item label="Address">
<a-row>
<a-button size="small" @click="outbound.settings.addAddress()">
+
</a-button>
</a-row>
<a-space direction="vertical">
<a-input-group compact v-for="(address, index) in outbound.settings.address">
<a-input v-model.trim="outbound.settings.address[index]" style="width: calc(100% - 40px)"></a-input>
<a-button type="delete" @click="outbound.settings.delAddress(index)">-</a-button>
</a-input-group>
</a-space>
</a-form-item>
<a-form-item label='MTU'>
<a-input placeholder="1420" type="number" min="0" v-model.number="outbound.settings.mtu"></a-input>
</a-form-item>
<a-form-item label='Workers'>
<a-input type="number" min="0" v-model.number="outbound.settings.workers"></a-input>
</a-form-item>
<a-form-item label='Domain Strategy'>
<a-select
v-model="outbound.settings.domainStrategy"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['', ...WireguardDomainStrategy]" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Peers">
<a-row>
<a-button type="primary" size="small"
@click="outbound.settings.addPeer()">
+
</a-button>
</a-row>
</a-form-item>
<a-form v-for="(peer, index) in outbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-divider style="margin:0;">
Peer [[ index + 1 ]]
<a-icon type="delete" @click="() => outbound.settings.delPeer(index)"
style="color: rgb(255, 77, 79);cursor: pointer;"/>
</a-divider>
<a-form-item label='Endpoint'>
<a-input v-model.trim="peer.endpoint"></a-input>
</a-form-item>
<a-form-item label='Public Key'>
<a-input v-model.trim="peer.publicKey"></a-input>
</a-form-item>
<a-form-item label='PreShared Key'>
<a-input v-model.trim="peer.preSharedKey"></a-input>
</a-form-item>
<a-form-item label='Keep Alive'>
<a-input type="number" min="0" v-model.number="peer.keepAlive"></a-input>
</a-form-item>
<a-form-item label="Allowed IPs">
<a-row>
<a-button size="small" @click="outbound.settings.addAllowedIP(index)">
+
</a-button>
</a-row>
<a-space direction="vertical">
<a-input-group compact v-for="(allowedIp, ipIndex) in peer.allowedIPs">
<a-input v-model.trim="peer.allowedIPs[ipIndex]" style="width: calc(100% - 40px)"></a-input>
<a-button type="delete" @click="outbound.settings.delAllowedIP(index, ipIndex)">-</a-button>
</a-input-group>
</a-space>
</a-form-item>
</a-form>
</template>
<!-- blackhole settings --> <!-- blackhole settings -->
<template v-if="outbound.protocol === Protocols.Blackhole"> <template v-if="outbound.protocol === Protocols.Blackhole">
<a-form-item label='Response Type'> <a-form-item label='Response Type'>
<a-select <a-select
v-model="outbound.settings.type" v-model="outbound.settings.type"
:dropdown-class-name="themeSwitcher.currentTheme"> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option> <a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
</template> </template>
<!-- dns settings --> <!-- dns settings -->
<template v-if="outbound.protocol === Protocols.DNS"> <template v-if="outbound.protocol === Protocols.DNS">
<a-form-item label='{{ i18n "pages.inbounds.network" }}'> <a-form-item label='{{ i18n "pages.inbounds.network" }}'>
<a-select <a-select
v-model="outbound.settings.network" v-model="outbound.settings.network"
:dropdown-class-name="themeSwitcher.currentTheme"> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option> <a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option>
</a-select> </a-select>
</a-form-item>
</template>
<!-- wireguard settings -->
<template v-if="outbound.protocol === Protocols.Wireguard">
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template>
{{ i18n "pages.xray.outbound.address" }} <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="outbound.settings.address"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon type="sync"
@click="[outbound.settings.pubKey, outbound.settings.secretKey] = Object.values(Wireguard.generateKeypair())">
</a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="outbound.settings.secretKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
<a-input disabled v-model="outbound.settings.pubKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.domainStrategy" }}'>
<a-select v-model="outbound.settings.domainStrategy" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="wds in ['', ...WireguardDomainStrategy]" :value="wds">[[ wds ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='MTU'>
<a-input-number v-model.number="outbound.settings.mtu"></a-input-number>
</a-form-item>
<a-form-item label='Workers'>
<a-input-number min="0" v-model.number="outbound.settings.workers"></a-input>
</a-form-item>
<a-form-item label='Kernel Mode'>
<a-switch v-model="outbound.settings.kernelMode"></a-switch>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template>
Reserved <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model="outbound.settings.reserved"></a-input>
</a-form-item>
<a-form-item label="Peers">
<a-button type="primary" size="small" @click="outbound.settings.addPeer()">+</a-button>
</a-form-item>
<a-form v-for="(peer, index) in outbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-divider style="margin:0;">
Peer [[ index + 1 ]]
<a-icon v-if="outbound.settings.peers.length>1" type="delete" @click="() => outbound.settings.delPeer(index)"
style="color: rgb(255, 77, 79);cursor: pointer;"/>
</a-divider>
<a-form-item label='{{ i18n "pages.xray.wireguard.endpoint" }}'>
<a-input v-model.trim="peer.endpoint"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
<a-input v-model.trim="peer.publicKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.psk" }}'>
<a-input v-model.trim="peer.psk"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
{{ i18n "pages.xray.wireguard.allowedIPs" }} <a-button type="primary" size="small" @click="peer.allowedIPs.push('')">+</a-button>
</template>
<template v-for="(aip, index) in peer.allowedIPs" style="margin-bottom: 10px;">
<a-input v-model.trim="peer.allowedIPs[index]">
<a-button v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)">-</a-button>
</a-input>
</template>
</a-form-item>
<a-form-item label='Keep Alive'>
<a-input-number v-model.number="peer.keepAlive" :min="0"></a-input>
</a-form-item>
</a-form>
</template> </template>
<!-- Address + Port --> <!-- Address + Port -->
@ -163,8 +171,9 @@
<a-form-item label='ID'> <a-form-item label='ID'>
<a-input v-model.trim="outbound.settings.id"></a-input> <a-input v-model.trim="outbound.settings.id"></a-input>
</a-form-item> </a-form-item>
<!-- vless settings -->
<template v-if="outbound.canEnableTlsFlow()"> <!-- vless settings -->
<template v-if="outbound.canEnableTlsFlow()">
<a-form-item label='Flow'> <a-form-item label='Flow'>
<a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option> <a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
@ -185,8 +194,9 @@
<a-input v-model.trim="outbound.settings.pass"></a-input> <a-input v-model.trim="outbound.settings.pass"></a-input>
</a-form-item> </a-form-item>
</template> </template>
<!-- shadowsocks -->
<template v-if="outbound.protocol === Protocols.Shadowsocks"> <!-- shadowsocks -->
<template v-if="outbound.protocol === Protocols.Shadowsocks">
<a-form-item label='{{ i18n "encryption" }}'> <a-form-item label='{{ i18n "encryption" }}'>
<a-select v-model="outbound.settings.method" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="outbound.settings.method" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="(method,method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option> <a-select-option v-for="(method,method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option>
@ -237,7 +247,7 @@
<a-select-option value="utp">uTP</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="wechat-video">WeChat</a-select-option>
<a-select-option value="dtls">DTLS 1.2</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="wireguard">WireGuard</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "password" }}'> <a-form-item label='{{ i18n "password" }}'>
@ -323,59 +333,54 @@
<!-- tls settings --> <!-- tls settings -->
<template v-if="outbound.canEnableTls()"> <template v-if="outbound.canEnableTls()">
<a-form-item label='{{ i18n "security" }}'> <a-form-item label='{{ i18n "security" }}'>
<a-radio-group v-model="outbound.stream.security" button-style="solid"> <a-radio-group v-model="outbound.stream.security" button-style="solid">
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button> <a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
<a-radio-button value="tls">TLS</a-radio-button> <a-radio-button value="tls">TLS</a-radio-button>
<a-radio-button v-if="outbound.canEnableReality()" value="reality">Reality</a-radio-button> <a-radio-button v-if="outbound.canEnableReality()" value="reality">Reality</a-radio-button>
</a-radio-group> </a-radio-group>
</a-form-item>
<template v-if="outbound.stream.isTls">
<a-form-item label="SNI" placeholder="Server Name Indication">
<a-input v-model.trim="outbound.stream.tls.serverName"></a-input>
</a-form-item> </a-form-item>
<template v-if="outbound.stream.isTls"> <a-form-item label="uTLS">
<a-form-item label="SNI" placeholder="Server Name Indication"> <a-select v-model="outbound.stream.tls.fingerprint" :dropdown-class-name="themeSwitcher.currentTheme">
<a-input v-model.trim="outbound.stream.tls.serverName"></a-input> <a-select-option value=''>None</a-select-option>
</a-form-item> <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
<a-form-item label="uTLS"> </a-select>
<a-select v-model="outbound.stream.tls.fingerprint" </a-form-item>
:dropdown-class-name="themeSwitcher.currentTheme"> <a-form-item label="ALPN">
<a-select-option value=''>None</a-select-option> <a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme"
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> v-model="outbound.stream.tls.alpn">
</a-select> <a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-form-item> </a-select>
<a-form-item label="ALPN"> </a-form-item>
<a-select <a-form-item label="Allow Insecure">
mode="multiple" <a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
</a-form-item>
:dropdown-class-name="themeSwitcher.currentTheme" </template>
v-model="outbound.stream.tls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option> <!-- reality settings -->
</a-select> <template v-if="outbound.stream.isReality">
</a-form-item> <a-form-item label='{{ i18n "domainName" }}'>
<a-form-item label="Allow Insecure"> <a-input v-model.trim="outbound.stream.reality.serverName"></a-input>
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch> </a-form-item>
</a-form-item> <a-form-item label="uTLS">
</template> <a-select v-model="outbound.stream.reality.fingerprint" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
<!-- reality settings --> </a-select>
<template v-if="outbound.stream.isReality"> </a-form-item>
<a-form-item label='{{ i18n "domainName" }}'> <a-form-item label="Short ID">
<a-input v-model.trim="outbound.stream.reality.serverName"></a-input> <a-input v-model.trim="outbound.stream.reality.shortId" style="width:250px"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="uTLS"> <a-form-item label="SpiderX">
<a-select v-model="outbound.stream.reality.fingerprint" <a-input v-model.trim="outbound.stream.reality.spiderX" style="width:250px"></a-input>
:dropdown-class-name="themeSwitcher.currentTheme"> </a-form-item>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> <a-form-item label="Public Key">
</a-select> <a-input v-model.trim="outbound.stream.reality.publicKey"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Short ID"> </template>
<a-input v-model.trim="outbound.stream.reality.shortId" style="width:250px"></a-input>
</a-form-item>
<a-form-item label="SpiderX">
<a-input v-model.trim="outbound.stream.reality.spiderX" style="width:250px"></a-input>
</a-form-item>
<a-form-item label="Public Key">
<a-input v-model.trim="outbound.stream.reality.publicKey"></a-input>
</a-form-item>
</template>
</template> </template>
</a-form> </a-form>
</a-tab-pane> </a-tab-pane>

View file

@ -119,7 +119,7 @@
v-model="freedomStrategy" v-model="freedomStrategy"
:dropdown-class-name="themeSwitcher.currentTheme" :dropdown-class-name="themeSwitcher.currentTheme"
style="width: 100%"> style="width: 100%">
<a-select-option v-for="s in outboundDomainStrategies" :value="s">[[ s ]]</a-select-option> <a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
</a-select> </a-select>
</template> </template>
</a-col> </a-col>
@ -527,7 +527,7 @@
tag: "direct", tag: "direct",
protocol: "freedom" protocol: "freedom"
}, },
outboundDomainStrategies: ["AsIs", "UseIP", "UseIPv4", "UseIPv6"], OutboundDomainStrategies: ["AsIs", "UseIP", "UseIPv4", "UseIPv6"],
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"], routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
settingsData: { settingsData: {
protocols: { protocols: {