瀏覽代碼

Outboud wireguard protocol support (#1451)

* Wireguard outbound settings modal window

* wireguard optional fields saniteze fix

* wireguard save domainStrategy and reserved(not implemented in form but will work)

---------

Co-authored-by: Сергей Павлюк <[email protected]>
Serge Pavlyuk 1 年之前
父節點
當前提交
b725ea7de5
共有 2 個文件被更改,包括 194 次插入0 次删除
  1. 112 0
      web/assets/js/model/outbound.js
  2. 82 0
      web/html/xui/form/outbound.html

+ 112 - 0
web/assets/js/model/outbound.js

@@ -8,6 +8,7 @@ const Protocols = {
     Shadowsocks: "shadowsocks",
     Socks: "socks",
     HTTP: "http",
+    Wireguard: "wireguard",
 };
 
 const SSMethods = {
@@ -53,11 +54,20 @@ const outboundDomainStrategies = [
     "UseIPv6"
 ]
 
+const WireguardDomainStrategy = [
+    "ForceIP",
+    "ForceIPv4",
+    "ForceIPv4v6",
+    "ForceIPv6",
+    "ForceIPv6v4"
+];
+
 Object.freeze(Protocols);
 Object.freeze(SSMethods);
 Object.freeze(TLS_FLOW_CONTROL);
 Object.freeze(ALPN_OPTION);
 Object.freeze(outboundDomainStrategies);
+Object.freeze(WireguardDomainStrategy);
 
 class CommonClass {
 
@@ -625,6 +635,7 @@ Outbound.Settings = class extends CommonClass {
             case Protocols.Shadowsocks: return new Outbound.ShadowsocksSettings();
             case Protocols.Socks: return new Outbound.SocksSettings();
             case Protocols.HTTP: return new Outbound.HttpSettings();
+            case Protocols.Wireguard: return new Outbound.WireguardSettings();
             default: return null;
         }
     }
@@ -640,6 +651,7 @@ Outbound.Settings = class extends CommonClass {
             case Protocols.Shadowsocks: return Outbound.ShadowsocksSettings.fromJson(json);
             case Protocols.Socks: return Outbound.SocksSettings.fromJson(json);
             case Protocols.HTTP: return Outbound.HttpSettings.fromJson(json);
+            case Protocols.Wireguard: return Outbound.WireguardSettings.fromJson(json);
             default: return null;
         }
     }
@@ -838,6 +850,106 @@ 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 {
     constructor(address, port, user, pass) {
         super();

+ 82 - 0
web/html/xui/form/outbound.html

@@ -44,6 +44,88 @@
     </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='keepAlive'>
+                <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 -->
 <template v-if="outbound.protocol === Protocols.Blackhole">
         <a-form-item label='Response Type'>